PageSourceSearch

https://darogan.cymru/app/themes/darogan/resources/scripts/favorites-manager.js?ver=1756808897

js darogan.cymru collected 2026-10-06 17:52:40 UTC 12,971 bytes, 428 lines download raw bytes

1/**
2 * Favorites Manager
3 * Handles saving and removing favorites for jobs and events
4 */
5
6function initFavoritesManager() {
7  'use strict';
8
9  // Global favorites manager
10  const favoritesManager = {
11    favorites: {},
12    isInitialized: false,
13
14    /**
15     * Initialize the favorites manager
16     */
17    init: function () {
18      if (this.isInitialized) return;
19
20      // Bind event listeners to favorite buttons
21      this.bindFavoriteButtons();
22
23      // Load user's favorites if logged in
24      this.loadFavorites();
25
26      this.isInitialized = true;
27    },
28
29    /**
30     * Bind click events to favorite buttons
31     */
32    bindFavoriteButtons: function () {
33      document.addEventListener('click', (e) => {
34        if (
35          e.target.closest('.favorite-btn') ||
36          e.target.closest('.dashboard-favorite-btn')
37        ) {
38          e.preventDefault();
39          const button =
40            e.target.closest('.favorite-btn') ||
41            e.target.closest('.dashboard-favorite-btn');
42          this.handleFavoriteClick(button);
43        }
44      });
45    },
46
47    /**
48     * Handle favorite button click
49     */
50    handleFavoriteClick: function (button) {
51      const itemId = button.dataset.id;
52      const itemType = button.dataset.type;
53      const itemTitle = button.dataset.title || '';
54      const itemPermalink = button.dataset.permalink || '';
55
56      if (!itemId || !itemType) {
57        return;
58      }
59
60      // Check if user is logged in
61      if (!this.isUserLoggedIn()) {
62        this.showMessage(daroganFavorites.strings.loginRequired, 'error');
63        return;
64      }
65
66      // Check if already favorited
67      const itemKey = itemType + '_' + itemId;
68      const isFavorited = this.favorites[itemKey];
69
70      if (isFavorited) {
71        this.removeFavorite(itemId, itemType, button);
72      } else {
73        this.saveFavorite(itemId, itemType, itemTitle, itemPermalink, button);
74      }
75    },
76
77    /**
78     * Save a favorite
79     */
80    saveFavorite: function (
81      itemId,
82      itemType,
83      itemTitle,
84      itemPermalink,
85      button
86    ) {
87      const data = {
88        item_id: itemId,
89        item_type: itemType,
90        item_title: itemTitle,
91        item_permalink: itemPermalink,
92      };
93
94      fetch(daroganFavorites.restUrl + 'favorites/save', {
95        method: 'POST',
96        headers: {
97          'Content-Type': 'application/json',
98          'X-WP-Nonce': daroganFavorites.nonce,
99        },
100        body: JSON.stringify(data),
101      })
102        .then((response) => {
103          return response.text().then((text) => {
104            try {
105              return JSON.parse(text);
106            } catch (e) {
107              throw new Error(
108                'Invalid JSON response: ' + text.substring(0, 200)
109              );
110            }
111          });
112        })
113        .then((data) => {
114          if (data.success) {
115            // Add to local favorites
116            const itemKey = itemType + '_' + itemId;
117            this.favorites[itemKey] = {
118              id: itemId,
119              type: itemType,
120              title: itemTitle,
121              permalink: itemPermalink,
122              date_added: new Date().toISOString(),
123            };
124
125            // Update button state
126            this.updateButtonState(button, true);
127
128            // Update dashboard if on dashboard page
129            this.updateDashboard();
130          } else {
131            this.showMessage(
132              data.data || daroganFavorites.strings.error,
133              'error'
134            );
135          }
136        })
137        .catch((error) => {
138          this.showMessage(daroganFavorites.strings.error, 'error');
139        });
140    },
141
142    /**
143     * Remove a favorite
144     */
145    removeFavorite: function (itemId, itemType, button) {
146      const data = {
147        item_id: itemId,
148        item_type: itemType,
149      };
150
151      fetch(daroganFavorites.restUrl + 'favorites/remove', {
152        method: 'POST',
153        headers: {
154          'Content-Type': 'application/json',
155          'X-WP-Nonce': daroganFavorites.nonce,
156        },
157        body: JSON.stringify(data),
158      })
159        .then((response) => {
160          return response.text().then((text) => {
161            try {
162              return JSON.parse(text);
163            } catch (e) {
164              throw new Error(
165                'Invalid JSON response: ' + text.substring(0, 200)
166              );
167            }
168          });
169        })
170        .then((data) => {
171          if (data.success) {
172            // Remove from local favorites
173            const itemKey = itemType + '_' + itemId;
174            delete this.favorites[itemKey];
175
176            // Update button state
177            this.updateButtonState(button, false);
178
179            // Update dashboard if on dashboard page
180            this.updateDashboard();
181          } else {
182            this.showMessage(
183              data.data || daroganFavorites.strings.error,
184              'error'
185            );
186          }
187        })
188        .catch((error) => {
189          console.error('Error removing favorite:', error);
190          this.showMessage(daroganFavorites.strings.error, 'error');
191        });
192    },
193
194    /**
195     * Load user's favorites
196     */
197    loadFavorites: function () {
198      if (!this.isUserLoggedIn()) return;
199
200      fetch(daroganFavorites.restUrl + 'favorites/get', {
201        method: 'GET',
202        headers: {
203          'X-WP-Nonce': daroganFavorites.nonce,
204        },
205      })
206        .then((response) => {
207          return response.text().then((text) => {
208            try {
209              return JSON.parse(text);
210            } catch (e) {
211              console.error('Failed to parse JSON:', e);
212              throw new Error(
213                'Invalid JSON response: ' + text.substring(0, 200)
214              );
215            }
216          });
217        })
218        .then((data) => {
219          if (data.success) {
220            this.favorites = data.favorites || {};
221            this.updateAllButtonStates();
222
223            // Trigger dashboard update after favorites are loaded
224            if (document.getElementById('saved-jobs-container')) {
225              this.updateDashboard();
226            }
227          } else {
228            console.error('AJAX request failed:', data);
229          }
230        })
231        .catch((error) => {
232          console.error('Error loading favorites:', error);
233        });
234    },
235
236    /**
237     * Update all favorite button states
238     */
239    updateAllButtonStates: function () {
240      const buttons = document.querySelectorAll(
241        '.favorite-btn, .dashboard-favorite-btn'
242      );
243      buttons.forEach((button) => {
244        const itemId = button.dataset.id;
245        const itemType = button.dataset.type;
246
247        if (itemId && itemType) {
248          const itemKey = itemType + '_' + itemId;
249          const isFavorited = this.favorites[itemKey];
250          this.updateButtonState(button, isFavorited);
251        }
252      });
253    },
254
255    /**
256     * Update button state
257     */
258    updateButtonState: function (button, isFavorited) {
259      const icon = button.querySelector('i');
260      const text = button.querySelector('.favorite-text');
261      const isDashboardButton = button.classList.contains(
262        'dashboard-favorite-btn'
263      );
264
265      if (isFavorited) {
266        button.classList.add('favorited');
267        if (icon) {
268          icon.className = isDashboardButton
269            ? 'fa-solid fa-heart'
270            : 'fa-solid fa-heart me-1';
271        }
272        if (text && !isDashboardButton) {
273          text.textContent = daroganFavorites.strings.savedToFavourites;
274        }
275      } else {
276        button.classList.remove('favorited');
277        if (icon) {
278          icon.className = isDashboardButton
279            ? 'fa-regular fa-heart'
280            : 'fa-regular fa-heart me-1';
281        }
282        if (text && !isDashboardButton) {
283          text.textContent = daroganFavorites.strings.saveToFavourites;
284        }
285      }
286    },
287
288    /**
289     * Update dashboard with favorites
290     */
291    updateDashboard: function () {
292      const savedJobsContainer = document.getElementById(
293        'saved-jobs-container'
294      );
295      if (!savedJobsContainer) return;
296
297      // Filter job favorites
298      const jobFavorites = Object.values(this.favorites).filter(
299        (fav) => fav.type === 'job'
300      );
301
302      if (jobFavorites.length === 0) {
303        // Show empty state
304        savedJobsContainer.innerHTML = `
305                    <div class="empty-state text-center py-5">
306                        <div class="row">
307                            <div class="col-8 offset-2 col-sm-6 offset-sm-3 col-lg-4 offset-lg-4 text-center mb-4">
308                                <img src="${daroganFavorites.themeUrl}/resources/images/sticker-marilwyd.svg" alt="" class="img-fluid" style="max-width: 200px;" />
309                            </div>
310                            <div class="col-12 col-lg-8 offset-lg-2 text-center">
311                                <h3 class="mb-3">${daroganFavorites.strings.getStarted}</h3>
312                                <p class="mb-4">${daroganFavorites.strings.noJobsSaved}</p>
313                                <a href="/jobs" class="btn btn-primary">${daroganFavorites.strings.exploreJobs}</a>
314                            </div>
315                        </div>
316                    </div>
317                `;
318      } else {
319        // Show loading state
320        savedJobsContainer.innerHTML = `
321                    <div class="text-center py-4">
322                        <div class="spinner-border" role="status">
323                            <span class="visually-hidden">Loading...</span>
324                        </div>
325                        <p class="mt-2">${daroganFavorites.strings.loadingJobs}</p>
326                    </div>
327                `;
328
329        // Fetch full job details
330        this.fetchJobDetails(jobFavorites, savedJobsContainer);
331      }
332    },
333
334    /**
335     * Fetch full job details for saved jobs
336     */
337    fetchJobDetails: function (jobFavorites, container) {
338      if (window.jobManager) {
339        window.jobManager.fetchJobDetails(jobFavorites, container);
340      }
341    },
342
343    /**
344     * Check if user is logged in
345     */
346    isUserLoggedIn: function () {
347      // Check if we're on a page with user data
348      const isLoggedIn = document.body.classList.contains('logged-in');
349      return isLoggedIn;
350    },
351
352    /**
353     * Show message to user
354     */
355    showMessage: function (message, type) {
356      // Create or find existing message container
357      let messageContainer = document.getElementById('favorites-message');
358      if (!messageContainer) {
359        messageContainer = document.createElement('div');
360        messageContainer.id = 'favorites-message';
361        messageContainer.style.cssText = `
362                    position: fixed;
363                    top: 20px;
364                    right: 20px;
365                    z-index: 9999;
366                    padding: 15px 20px;
367                    border-radius: 8px;
368                    color: white;
369                    font-weight: 600;
370                    font-size: 14px;
371                    max-width: 350px;
372                    word-wrap: break-word;
373                    line-height: 1.4;
374                    transition: all 0.3s ease;
375                    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
376                    border: 2px solid;
377                    opacity: 0;
378                    transform: translateX(100%);
379                    display: flex;
380                    align-items: center;
381                    gap: 8px;
382                `;
383        document.body.appendChild(messageContainer);
384      }
385
386      // Set message content and style based on type
387      const icon = type === 'success' ? '✓' : '✕';
388      messageContainer.innerHTML = `<span style="font-size: 16px;">${icon}</span><span>${message}</span>`;
389
390      if (type === 'success') {
391        messageContainer.style.backgroundColor = 'var(--darogan-red)';
392        messageContainer.style.borderColor = 'var(--darogan-red)';
393        messageContainer.style.color = 'white';
394      } else {
395        messageContainer.style.backgroundColor = '#dc3545';
396        messageContainer.style.borderColor = '#dc3545';
397        messageContainer.style.color = 'white';
398      }
399
400      // Show message with animation
401      messageContainer.style.opacity = '1';
402      messageContainer.style.transform = 'translateX(0)';
403
404      // Hide message after 3 seconds with animation
405      setTimeout(() => {
406        messageContainer.style.opacity = '0';
407        messageContainer.style.transform = 'translateX(100%)';
408      }, 3000);
409    },
410  };
411
412  // Make it available globally
413  window.favoritesManager = favoritesManager;
414
415  // Initialize when DOM is ready
416  if (document.readyState === 'loading') {
417    document.addEventListener('DOMContentLoaded', () => {
418      favoritesManager.init();
419    });
420  } else {
421    favoritesManager.init();
422  }
423
424  return favoritesManager;
425}
426
427// Make it available globally for WordPress enqueue
428window.initFavoritesManager = initFavoritesManager;

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.