PageSourceSearch

https://lg-occasion.fr/app/plugins/alcopa/public/assets/js/01-personal-info.js?ver=1.0.0

js lg-occasion.fr collected 2026-10-10 00:49:42 UTC 12,675 bytes, 418 lines download raw bytes

1/**
2 * Handle the personal info step
3 */
4
5class PersonalInfoManager {
6  constructor() {
7    this.suggestions = [];
8    this.selectedSuggestionIndex = -1;
9    this.suggestionsContainer = null;
10    this.initialized = false;
11    this.form = null;
12    this.nextButton = null;
13    this.isFormValid = false;
14
15    // Bind methods to maintain 'this' context
16    this.setupAddressAutocomplete = this.setupAddressAutocomplete.bind(this);
17    this.getSuggestionsContainer = this.getSuggestionsContainer.bind(this);
18    this.showAddressSuggestions = this.showAddressSuggestions.bind(this);
19    this.hideSuggestions = this.hideSuggestions.bind(this);
20    this.navigateSuggestions = this.navigateSuggestions.bind(this);
21    this.highlightSuggestion = this.highlightSuggestion.bind(this);
22    this.selectSuggestion = this.selectSuggestion.bind(this);
23    this.validateForm = this.validateForm.bind(this);
24    this.showError = this.showError.bind(this);
25    this.hideError = this.hideError.bind(this);
26    this.handleInput = this.handleInput.bind(this);
27    this.updateNextButtonState = this.updateNextButtonState.bind(this);
28
29    // Initialize when DOM is ready
30    if (document.readyState === "loading") {
31      document.addEventListener("DOMContentLoaded", () => this.init());
32    } else {
33      this.init();
34    }
35  }
36
37  init() {
38    console.log("PersonalInfoManager initializing...");
39
40    // Try to initialize now and retry after a short delay if elements aren't ready
41    this.tryInitialize();
42
43    // Retry initialization after a delay to ensure DOM elements are ready
44    setTimeout(() => {
45      if (!this.initialized) {
46        console.log("Retrying initialization...");
47        this.tryInitialize();
48      }
49    }, 1000);
50  }
51
52  tryInitialize() {
53    const addressInput = document.getElementById("address");
54    const step1Form = document.getElementById("step1");
55    const nextButton = document.getElementById("step1-next");
56
57    if (addressInput && step1Form && nextButton) {
58      console.log("Form elements found, initializing validation");
59      this.form = step1Form;
60      this.nextButton = nextButton;
61      this.getSuggestionsContainer();
62      this.setupAddressAutocomplete();
63      this.setupFormValidation();
64      this.initialized = true;
65    } else {
66      console.warn("Form elements not found, initialization delayed");
67    }
68  }
69
70  setupFormValidation() {
71    // Add input event listeners to all form inputs
72    const inputs = this.form.querySelectorAll("input");
73    inputs.forEach((input) => {
74      input.addEventListener("input", () => {
75        this.handleInput(input);
76        this.updateNextButtonState();
77      });
78      input.addEventListener("blur", () => {
79        this.handleInput(input);
80        this.updateNextButtonState();
81      });
82    });
83
84    // Add click event listener to the next button
85    this.nextButton.addEventListener("click", (e) => {
86      e.preventDefault();
87      if (this.validateForm()) {
88        // If validation passes, proceed to next step
89        document.getElementById("step1").classList.add("hidden");
90        document.getElementById("step2").classList.remove("hidden");
91        
92        // Scroll to top smoothly
93        window.scrollTo({
94          top: 0,
95          behavior: 'smooth'
96        });
97      }
98    });
99
100    // Initial button state
101    this.updateNextButtonState();
102  }
103
104  updateNextButtonState() {
105    const inputs = this.form.querySelectorAll("input");
106    this.isFormValid = Array.from(inputs).every((input) =>
107      this.validateInput(input)
108    );
109
110    if (this.isFormValid) {
111      this.nextButton.classList.remove("opacity-50", "cursor-not-allowed");
112      this.nextButton.classList.add("hover:bg-red-100");
113      this.nextButton.disabled = false;
114    } else {
115      this.nextButton.classList.add("opacity-50", "cursor-not-allowed");
116      this.nextButton.classList.remove("hover:bg-red-100");
117      this.nextButton.disabled = true;
118    }
119  }
120
121  handleInput(input) {
122    const isValid = this.validateInput(input);
123    if (isValid) {
124      this.hideError(input);
125      input.classList.remove("border-red-500");
126      input.classList.add("border-gray-300");
127    } else {
128      this.showError(input);
129      input.classList.remove("border-gray-300");
130      input.classList.add("border-red-500");
131    }
132  }
133
134  validateInput(input) {
135    const value = input.value.trim();
136
137    // Check if required field is empty
138    if (input.required && !value) {
139      return false;
140    }
141
142    // Validate based on input type and pattern
143    switch (input.type) {
144      case "email":
145        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
146      case "tel":
147        return /^(?:(?:\+|00)33|0)\s*[1-9](?:[\s.-]*\d{2}){4}$/.test(value);
148      default:
149        if (input.pattern) {
150          return new RegExp(input.pattern).test(value);
151        }
152        return true;
153    }
154  }
155
156  validateForm() {
157    let isValid = true;
158    const inputs = this.form.querySelectorAll("input");
159
160    inputs.forEach((input) => {
161      if (!this.validateInput(input)) {
162        this.showError(input);
163        input.classList.remove("border-gray-300");
164        input.classList.add("border-red-500");
165        isValid = false;
166      } else {
167        this.hideError(input);
168        input.classList.remove("border-red-500");
169        input.classList.add("border-gray-300");
170      }
171    });
172
173    return isValid;
174  }
175
176  showError(input) {
177    const errorMessage = input.nextElementSibling;
178    if (errorMessage && errorMessage.classList.contains("error-message text-sm")) {
179      errorMessage.classList.remove("hidden");
180    }
181  }
182
183  hideError(input) {
184    const errorMessage = input.nextElementSibling;
185    if (errorMessage && errorMessage.classList.contains("error-message text-sm")) {
186      errorMessage.classList.add("hidden");
187    }
188  }
189
190  getSuggestionsContainer() {
191    // Get the existing suggestions container
192    this.suggestionsContainer = document.getElementById("address-suggestions");
193
194    if (!this.suggestionsContainer) {
195      console.error("Suggestions container not found, creating one");
196      // Create new suggestions container as fallback
197      this.suggestionsContainer = document.createElement("div");
198      this.suggestionsContainer.id = "address-suggestions";
199      this.suggestionsContainer.className =
200        "absolute z-10 w-full bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-y-auto mt-1 hidden";
201
202      // Find the address field container and insert suggestions container after it
203      const addressField = document.getElementById("address");
204      if (addressField) {
205        console.log("Attaching suggestions container to address field parent");
206        const parentDiv = addressField.parentNode;
207        parentDiv.style.position = "relative";
208        parentDiv.appendChild(this.suggestionsContainer);
209      } else {
210        console.error('Address field not found with ID "address"');
211      }
212    }
213  }
214
215  setupAddressAutocomplete() {
216    const addressInput = document.getElementById("address");
217
218    if (!addressInput) {
219      console.error("Address input not found");
220      return;
221    }
222
223    if (!this.suggestionsContainer) {
224      console.error("Suggestions container not created");
225      return;
226    }
227
228    console.log("Setting up event listeners for address autocomplete");
229
230    // Handle input changes
231    addressInput.addEventListener(
232      "input",
233      this.debounce(async (e) => {
234        console.log("Input event fired");
235        const query = e.target.value.trim();
236        this.selectedSuggestionIndex = -1;
237
238        if (query.length < 3) {
239          this.hideSuggestions();
240          return;
241        }
242
243        try {
244          console.log(`Fetching address suggestions for query: ${query}`);
245          const response = await fetch(
246            `https://api-adresse.data.gouv.fr/search/?q=${encodeURIComponent(
247              query
248            )}`,
249            { method: "GET" }
250          );
251
252          if (!response.ok) throw new Error("Network response was not ok");
253
254          const data = await response.json();
255          console.log(`Received ${data.features?.length || 0} suggestions`);
256          this.suggestions = data.features || [];
257          this.showAddressSuggestions();
258        } catch (error) {
259          console.error("Error fetching address suggestions:", error);
260          this.hideSuggestions();
261        }
262      }, 300)
263    );
264
265    // Handle keyboard navigation
266    addressInput.addEventListener("keydown", (e) => {
267      if (["ArrowDown", "ArrowUp", "Enter", "Escape"].includes(e.ke
267y)) {
268        console.log(`Key pressed: ${e.key}`);
269      }
270
271      switch (e.key) {
272        case "ArrowDown":
273          e.preventDefault();
274          this.navigateSuggestions("down");
275          break;
276        case "ArrowUp":
277          e.preventDefault();
278          this.navigateSuggestions("up");
279          break;
280        case "Enter":
281          e.preventDefault();
282          if (this.selectedSuggestionIndex >= 0) {
283            this.selectSuggestion(this.selectedSuggestionIndex);
284          }
285          break;
286        case "Escape":
287          this.hideSuggestions();
288          break;
289      }
290    });
291
292    // Hide suggestions when clicking outside
293    document.addEventListener("click", (e) => {
294      if (
295        !addressInput.contains(e.target) &&
296        !this.suggestionsContainer.contains(e.target)
297      ) {
298        this.hideSuggestions();
299      }
300    });
301
302    console.log("Address autocomplete setup complete");
303  }
304
305  showAddressSuggestions() {
306    if (!this.suggestions.length) {
307      this.hideSuggestions();
308      return;
309    }
310
311    console.log(`Displaying ${this.suggestions.length} suggestions`);
312    this.suggestionsContainer.innerHTML = "";
313
314    this.suggestions.forEach((suggestion, index) => {
315      const item = document.createElement("div");
316      item.className =
317        "px-4 py-2 hover:bg-gray-100 cursor-pointer font-inter text-sm";
318      item.textContent = suggestion.properties.label;
319
320      item.addEventListener("click", () => {
321        this.selectSuggestion(index);
322      });
323
324      item.addEventListener("mouseenter", () => {
325        this.selectedSuggestionIndex = index;
326        this.highlightSuggestion();
327      });
328
329      this.suggestionsContainer.appendChild(item);
330    });
331
332    // Use Tailwind's hidden class - remove it to show the container
333    this.suggestionsContainer.classList.remove("hidden");
334  }
335
336  hideSuggestions() {
337    if (this.suggestionsContainer) {
338      // Use Tailwind's hidden class - add it to hide the container
339      this.suggestionsContainer.classList.add("hidden");
340    }
341  }
342
343  navigateSuggestions(direction) {
344    if (!this.suggestions.length) return;
345
346    if (direction === "down") {
347      this.selectedSuggestionIndex = Math.min(
348        this.selectedSuggestionIndex + 1,
349        this.suggestions.length - 1
350      );
351    } else {
352      this.selectedSuggestionIndex = Math.max(
353        this.selectedSuggestionIndex - 1,
354        -1
355      );
356    }
357
358    this.highlightSuggestion();
359  }
360
361  highlightSuggestion() {
362    const items = this.suggestionsContainer.querySelectorAll("div");
363    items.forEach((item, index) => {
364      if (index === this.selectedSuggestionIndex) {
365        item.classList.add("bg-gray-100");
366      } else {
367        item.classList.remove("bg-gray-100");
368      }
369    });
370
371    // Ensure the highlighted item is visible
372    if (this.selectedSuggestionIndex >= 0) {
373      const selectedItem = items[this.selectedSuggestionIndex];
374      if (selectedItem) {
375        selectedItem.scrollIntoView({ block: "nearest" });
376      }
377    }
378  }
379
380  selectSuggestion(index) {
381    const suggestion = this.suggestions[index];
382    if (!suggestion) return;
383
384    const properties = suggestion.properties;
385    console.log("Selected address:", properties);
386
387    // Fill in the form fields
388    document.getElementById("address").value = properties.name || "";
389    document.getElementById("city").value = properties.city || "";
390    document.getElementById("zipCode").value = properties.postcode || "";
391
392    // Ensure suggestions are hidden after selection
393    this.hideSuggestions();
394
395    // Clear the suggestions array to prevent them from showing again
396    this.suggestions = [];
397
398    // Update the next button state after autocomplete
399    this.updateNextButtonState();
400  }
401
402  // Utility function to debounce API calls
403  debounce(func, wait) {
404    let timeout;
405    return function executedFunction(...args) {
406      const later = () => {
407        clearTimeout(timeout);
408        func(...args);
409      };
410      clearTimeout(timeout);
411      timeout = setTimeout(later, wait);
412    };
413  }
414}
415
416// Initialize the personal info manager
417console.log("Initializing PersonalInfoManager");
418const manager = new PersonalInfoManager();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.