diff --git a/frontend/public/sw-custom.js b/frontend/public/sw-custom.js new file mode 100644 index 0000000..f10c927 --- /dev/null +++ b/frontend/public/sw-custom.js @@ -0,0 +1,97 @@ +// Service Worker personnalisé pour gérer les notifications push +// Ce fichier sera fusionné avec le service worker généré par vite-plugin-pwa + +// Écouter les événements de notification +self.addEventListener('notificationclick', (event) => { + console.log('Notification clicked:', event.notification) + + event.notification.close() + + // Récupérer le lien depuis les données de la notification + const link = event.notification.data?.link || event.notification.data?.url || '/' + + // Ouvrir ou focus la fenêtre/clients + event.waitUntil( + clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { + // Si une fenêtre est déjà ouverte, la focus + for (let i = 0; i < clientList.length; i++) { + const client = clientList[i] + if (client.url && 'focus' in client) { + // Naviguer vers le lien si nécessaire + if (link && !client.url.includes(link.split('/')[1])) { + return client.navigate(link).then(() => client.focus()) + } + return client.focus() + } + } + + // Sinon, ouvrir une nouvelle fenêtre + if (clients.openWindow) { + return clients.openWindow(link || '/') + } + }) + ) +}) + +// Écouter les messages du client pour afficher des notifications +self.addEventListener('message', (event) => { + console.log('Service Worker received message:', event.data) + + if (event.data && event.data.type === 'SHOW_NOTIFICATION') { + const { title, options } = event.data + + event.waitUntil( + self.registration.showNotification(title, { + icon: '/icon-192x192.png', + badge: '/icon-96x96.png', + tag: 'lediscord-notification', + requireInteraction: false, + vibrate: [200, 100, 200], + data: { + link: options.link || '/', + notificationId: options.data?.notificationId + }, + ...options + }) + ) + } +}) + +// Écouter les push events (pour les vraies push notifications depuis le serveur) +self.addEventListener('push', (event) => { + console.log('Push event received:', event) + + let notificationData = { + title: 'LeDiscord', + body: 'Vous avez une nouvelle notification', + icon: '/icon-192x192.png', + badge: '/icon-96x96.png', + tag: 'lediscord-notification', + data: { + link: '/' + } + } + + // Si des données sont envoyées avec le push + if (event.data) { + try { + const data = event.data.json() + notificationData = { + ...notificationData, + title: data.title || notificationData.title, + body: data.body || data.message || notificationData.body, + data: { + link: data.link || '/', + notificationId: data.notificationId + } + } + } catch (e) { + console.error('Error parsing push data:', e) + } + } + + event.waitUntil( + self.registration.showNotification(notificationData.title, notificationData) + ) +}) + diff --git a/frontend/src/App.vue b/frontend/src/App.vue index cb55266..5d5fc8c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -8,15 +8,24 @@ \ No newline at end of file diff --git a/frontend/src/components/PWAInstallTutorial.vue b/frontend/src/components/PWAInstallTutorial.vue new file mode 100644 index 0000000..d6f7c32 --- /dev/null +++ b/frontend/src/components/PWAInstallTutorial.vue @@ -0,0 +1,243 @@ + + + + diff --git a/frontend/src/components/VlogComments.vue b/frontend/src/components/VlogComments.vue index 0058391..4834a4c 100644 --- a/frontend/src/components/VlogComments.vue +++ b/frontend/src/components/VlogComments.vue @@ -97,8 +97,7 @@ import { useToast } from 'vue-toastification' import { MessageSquare, User } from 'lucide-vue-next' import Mentions from '@/components/Mentions.vue' import MentionInput from '@/components/MentionInput.vue' -import { formatDistanceToNow } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatRelativeDateInFrenchTimezone } from '@/utils/dateUtils' import { getMediaUrl } from '@/utils/axios' import axios from '@/utils/axios' @@ -130,7 +129,7 @@ const commentMentions = ref([]) const currentUser = computed(() => authStore.user) function formatDate(date) { - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } function getAvatarUrl(avatarUrl) { diff --git a/frontend/src/layouts/DefaultLayout.vue b/frontend/src/layouts/DefaultLayout.vue index 215ac7b..6ecbd79 100644 --- a/frontend/src/layouts/DefaultLayout.vue +++ b/frontend/src/layouts/DefaultLayout.vue @@ -204,6 +204,13 @@ + + + /iPhone|iPad|iPod/i.test(navigator.userAgent)) const user = computed(() => authStore.user) const notifications = computed(() => authStore.notifications) const unreadNotifications = computed(() => authStore.unreadCount) function formatDate(date) { - return format(new Date(date), 'dd MMM à HH:mm', { locale: fr }) + return formatDateInFrenchTimezone(date, 'dd MMM à HH:mm') } async function logout() { @@ -336,11 +346,18 @@ async function handleNotificationClick(notification) { await authStore.markNotificationRead(notification.id) } - if (notification.link) { - router.push(notification.link) - } - showNotifications.value = false + + if (notification.link) { + // Gérer les liens de posts différemment car il n'y a pas de route /posts/:id + if (notification.link.startsWith('/posts/')) { + const postId = notification.link.split('/posts/')[1] + // Naviguer vers /posts et passer l'ID en query pour scroll + router.push({ path: '/posts', query: { highlight: postId } }) + } else { + router.push(notification.link) + } + } } // PWA Installation logic @@ -398,20 +415,19 @@ async function handleInstallApp() { console.error('Erreur lors de l\'installation:', error) } } else if (isMobile.value) { - // Sur mobile sans beforeinstallprompt, afficher les instructions + // Sur mobile sans beforeinstallprompt, afficher le tutoriel showUserMenu.value = false - const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent) - if (isIOS) { - alert('Sur iOS :\n1. Appuyez sur le bouton de partage (□↑) en bas de l\'écran\n2. Faites défiler et sélectionnez "Sur l\'écran d\'accueil"\n3. Appuyez sur "Ajouter"') - } else { - alert('Sur Android :\n1. Appuyez sur le menu (⋮) en haut à droite\n2. Sélectionnez "Ajouter à l\'écran d\'accueil" ou "Installer l\'application"\n3. Confirmez l\'installation') - } + showPWAInstructions.value = true } } onMounted(async () => { checkIfMobile() - await authStore.fetchCurrentUser() + + // Restaurer la session si un token existe + if (authStore.token) { + await authStore.fetchCurrentUser() + } if (authStore.isAuthenticated) { await fetchNotifications() diff --git a/frontend/src/services/notificationService.js b/frontend/src/services/notificationService.js index 83df873..5c54862 100644 --- a/frontend/src/services/notificationService.js +++ b/frontend/src/services/notificationService.js @@ -51,11 +51,25 @@ class NotificationService { .slice(0, result.newCount - result.previousCount) if (newUnreadNotifications.length > 0) { + // Ne pas afficher de notification si l'app est en focus + // (pour éviter les doublons avec les notifications dans l'app) + if (document.hasFocus()) { + return + } + // Afficher une notification push pour la plus récente const latestNotification = newUnreadNotifications[0] + + // Gérer les liens de posts différemment + let link = latestNotification.link || '/' + if (link.startsWith('/posts/')) { + const postId = link.split('/posts/')[1] + link = `/posts?highlight=${postId}` + } + await this.showPushNotification(latestNotification.title, { body: latestNotification.message, - link: latestNotification.link || '/', + link: link, data: { notificationId: latestNotification.id } }) } @@ -98,16 +112,44 @@ class NotificationService { const hasPermission = await this.requestNotificationPermission() if (!hasPermission) return - // Si on est dans un service worker, utiliser la notification API du SW + // Toujours utiliser le service worker pour les notifications push + // Cela permet aux notifications de fonctionner même quand l'app est fermée if ('serviceWorker' in navigator) { try { const registration = await navigator.serviceWorker.ready + + // Envoyer un message au service worker pour afficher la notification + // Cela permet de gérer les clics correctement + registration.active?.postMessage({ + type: 'SHOW_NOTIFICATION', + title, + options: { + body: options.body || options.message || '', + icon: '/icon-192x192.png', + badge: '/icon-96x96.png', + tag: 'lediscord-notification', + requireInteraction: false, + vibrate: [200, 100, 200], + data: { + link: options.link || '/', + notificationId: options.data?.notificationId + }, + ...options + } + }) + + // Aussi utiliser l'API directe comme fallback await registration.showNotification(title, { icon: '/icon-192x192.png', badge: '/icon-96x96.png', tag: 'lediscord-notification', requireInteraction: false, vibrate: [200, 100, 200], + data: { + link: options.link || '/', + notificationId: options.data?.notificationId + }, + body: options.body || options.message || '', ...options }) return @@ -116,12 +158,13 @@ class NotificationService { } } - // Fallback: notification native du navigateur + // Fallback: notification native du navigateur (seulement si le SW n'est pas disponible) const notification = new Notification(title, { icon: '/icon-192x192.png', badge: '/icon-96x96.png', tag: 'lediscord-notification', requireInteraction: false, + body: options.body || options.message || '', ...options }) @@ -130,7 +173,10 @@ class NotificationService { notification.close() if (options.link) { - window.location.href = options.link + // Utiliser le router si disponible + if (window.location.pathname !== options.link) { + window.location.href = options.link + } } } diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js index 546e1dd..b823eb0 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.js @@ -9,7 +9,7 @@ export const useAuthStore = defineStore('auth', () => { const token = ref(localStorage.getItem('token')) const toast = useToast() - const isAuthenticated = computed(() => !!token.value) + const isAuthenticated = computed(() => !!token.value && !!user.value) const isAdmin = computed(() => user.value?.is_admin || false) if (token.value) { diff --git a/frontend/src/utils/dateUtils.js b/frontend/src/utils/dateUtils.js new file mode 100644 index 0000000..3285090 --- /dev/null +++ b/frontend/src/utils/dateUtils.js @@ -0,0 +1,66 @@ +import { format, formatDistanceToNow } from 'date-fns' +import { fr } from 'date-fns/locale' +import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz' + +// Fuseau horaire français +const FRENCH_TIMEZONE = 'Europe/Paris' + +/** + * Convertit une date UTC en date du fuseau horaire français + */ +function toFrenchTimezone(date) { + if (!date) return null + const dateObj = date instanceof Date ? date : new Date(date) + return utcToZonedTime(dateObj, FRENCH_TIMEZONE) +} + +/** + * Formate une date dans le fuseau horaire français + */ +export function formatDateInFrenchTimezone(date, formatStr = 'dd MMM à HH:mm') { + if (!date) return '' + const frenchDate = toFrenchTimezone(date) + return format(frenchDate, formatStr, { locale: fr }) +} + +/** + * Formate une date relative dans le fuseau horaire français + */ +export function formatRelativeDateInFrenchTimezone(date) { + if (!date) return '' + const frenchDate = toFrenchTimezone(date) + return formatDistanceToNow(frenchDate, { addSuffix: true, locale: fr }) +} + +/** + * Formate une date complète dans le fuseau horaire français + */ +export function formatFullDateInFrenchTimezone(date) { + return formatDateInFrenchTimezone(date, 'EEEE d MMMM yyyy à HH:mm') +} + +/** + * Formate une date courte dans le fuseau horaire français + */ +export function formatShortDateInFrenchTimezone(date) { + return formatDateInFrenchTimezone(date, 'dd/MM/yyyy') +} + +/** + * Formate une date pour un input datetime-local dans le fuseau horaire français + */ +export function formatDateForInputInFrenchTimezone(date) { + if (!date) return '' + const frenchDate = toFrenchTimezone(date) + return format(frenchDate, "yyyy-MM-dd'T'HH:mm", { locale: fr }) +} + +/** + * Convertit une date du fuseau horaire français vers UTC pour l'envoyer au backend + */ +export function convertFrenchTimezoneToUTC(date) { + if (!date) return null + const dateObj = date instanceof Date ? date : new Date(date) + return zonedTimeToUtc(dateObj, FRENCH_TIMEZONE) +} + diff --git a/frontend/src/views/Admin.vue b/frontend/src/views/Admin.vue index bbc7db2..ac663df 100644 --- a/frontend/src/views/Admin.vue +++ b/frontend/src/views/Admin.vue @@ -1239,8 +1239,7 @@ import { TestTube, Plus } from 'lucide-vue-next' -import { formatDistanceToNow } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatRelativeDateInFrenchTimezone } from '@/utils/dateUtils' import LoadingLogo from '@/components/LoadingLogo.vue' const authStore = useAuthStore() @@ -1710,7 +1709,7 @@ function getPriorityBadgeClass(priority) { } function formatDate(date) { - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } // Gestion des tickets diff --git a/frontend/src/views/AlbumDetail.vue b/frontend/src/views/AlbumDetail.vue index 99daa63..f20fce0 100644 --- a/frontend/src/views/AlbumDetail.vue +++ b/frontend/src/views/AlbumDetail.vue @@ -567,8 +567,7 @@ import { useAuthStore } from '@/stores/auth' import { useToast } from 'vue-toastification' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { format, formatDistanceToNow } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatDateInFrenchTimezone, formatRelativeDateInFrenchTimezone } from '@/utils/dateUtils' import { ArrowLeft, Image, @@ -618,7 +617,7 @@ const totalSize = computed(() => ) function formatDate(date) { - return format(new Date(date), 'dd MMMM yyyy', { locale: fr }) + return formatDateInFrenchTimezone(date, 'dd MMMM yyyy') } function formatBytes(bytes) { diff --git a/frontend/src/views/Albums.vue b/frontend/src/views/Albums.vue index 4a488cb..48d35d1 100644 --- a/frontend/src/views/Albums.vue +++ b/frontend/src/views/Albums.vue @@ -335,8 +335,7 @@ import { useToast } from 'vue-toastification' import { useRouter } from 'vue-router' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { formatDistanceToNow, format } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatRelativeDateInFrenchTimezone, formatShortDateInFrenchTimezone } from '@/utils/dateUtils' import { Plus, Image, @@ -384,11 +383,11 @@ const uploadSuccess = ref([]) const isDragOver = ref(false) function formatRelativeDate(date) { - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } function formatDate(date) { - return format(new Date(date), 'dd/MM/yyyy', { locale: fr }) + return formatShortDateInFrenchTimezone(date) } function formatFileSize(bytes) { diff --git a/frontend/src/views/EventDetail.vue b/frontend/src/views/EventDetail.vue index b2bebc0..4065f37 100644 --- a/frontend/src/views/EventDetail.vue +++ b/frontend/src/views/EventDetail.vue @@ -342,8 +342,7 @@ import { useAuthStore } from '@/stores/auth' import { useToast } from 'vue-toastification' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { format, formatDistanceToNow } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatFullDateInFrenchTimezone, formatRelativeDateInFrenchTimezone, formatDateForInputInFrenchTimezone, convertFrenchTimezoneToUTC } from '@/utils/dateUtils' import { ArrowLeft, Calendar, @@ -379,11 +378,11 @@ const canEdit = computed(() => ) function formatDate(date) { - return format(new Date(date), 'EEEE d MMMM yyyy à HH:mm', { locale: fr }) + return formatFullDateInFrenchTimezone(date) } function formatRelativeDate(date) { - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } function getParticipationClass(status) { @@ -444,7 +443,7 @@ async function fetchEvent() { editForm.value = { title: event.value.title, description: event.value.description || '', - date: format(new Date(event.value.date), "yyyy-MM-dd'T'HH:mm", { locale: fr }), + date: formatDateForInputInFrenchTimezone(event.value.date), location: event.value.location || '' } } catch (error) { @@ -470,7 +469,7 @@ async function updateEvent() { try { const response = await axios.put(`/api/events/${event.value.id}`, { ...editForm.value, - date: new Date(editForm.value.date).toISOString() + date: convertFrenchTimezoneToUTC(new Date(editForm.value.date)).toISOString() }) event.value = response.data showEditModal.value = false diff --git a/frontend/src/views/Events.vue b/frontend/src/views/Events.vue index 3bed5d4..0bcbae9 100644 --- a/frontend/src/views/Events.vue +++ b/frontend/src/views/Events.vue @@ -333,8 +333,7 @@ import { useToast } from 'vue-toastification' import { useRouter } from 'vue-router' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { formatDistanceToNow, format } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatRelativeDateInFrenchTimezone, formatShortDateInFrenchTimezone } from '@/utils/dateUtils' import { Plus, Calendar, @@ -380,11 +379,11 @@ const filteredEvents = computed(() => { }) function formatRelativeDate(date) { - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } function formatDate(date) { - return format(new Date(date), 'dd/MM/yyyy', { locale: fr }) + return formatShortDateInFrenchTimezone(date) } function openEvent(event) { @@ -481,9 +480,9 @@ async function createEvent() { const eventData = { title: newEvent.value.title, description: newEvent.value.description, - date: new Date(newEvent.value.date).toISOString(), + date: convertFrenchTimezoneToUTC(new Date(newEvent.value.date)).toISOString(), location: newEvent.value.location, - end_date: newEvent.value.end_date ? new Date(newEvent.value.end_date).toISOString() : null, + end_date: newEvent.value.end_date ? convertFrenchTimezoneToUTC(new Date(newEvent.value.end_date)).toISOString() : null, is_private: newEvent.value.is_private, invited_user_ids: newEvent.value.is_private ? newEvent.value.invited_user_ids : null } diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index 0bfa34e..5f3599c 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -220,8 +220,7 @@ import { ref, computed, onMounted } from 'vue' import { useAuthStore } from '@/stores/auth' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { format, formatDistanceToNow } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatFullDateInFrenchTimezone, formatRelativeDateInFrenchTimezone } from '@/utils/dateUtils' import { Calendar, TrendingUp, @@ -248,11 +247,11 @@ const recentPosts = computed(() => stats.value.recent_posts || 0) const activeMembers = computed(() => stats.value.total_users || 0) function formatDate(date) { - return format(new Date(date), 'EEEE d MMMM à HH:mm', { locale: fr }) + return formatFullDateInFrenchTimezone(date) } function formatRelativeDate(date) { - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } async function fetchDashboardData() { diff --git a/frontend/src/views/Information.vue b/frontend/src/views/Information.vue index 21baca2..2038a3f 100644 --- a/frontend/src/views/Information.vue +++ b/frontend/src/views/Information.vue @@ -103,8 +103,7 @@ diff --git a/frontend/src/views/Profile.vue b/frontend/src/views/Profile.vue index 2ee8498..c4bdddd 100644 --- a/frontend/src/views/Profile.vue +++ b/frontend/src/views/Profile.vue @@ -159,8 +159,7 @@ import { useAuthStore } from '@/stores/auth' import { useToast } from 'vue-toastification' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { format } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatDateInFrenchTimezone } from '@/utils/dateUtils' import { User, Camera } from 'lucide-vue-next' const authStore = useAuthStore() @@ -179,7 +178,7 @@ const form = ref({ function formatDate(date) { if (!date) return '' - return format(new Date(date), 'MMMM yyyy', { locale: fr }) + return formatDateInFrenchTimezone(date, 'MMMM yyyy') } function resetForm() { diff --git a/frontend/src/views/Register.vue b/frontend/src/views/Register.vue index 88d93fe..45084b5 100644 --- a/frontend/src/views/Register.vue +++ b/frontend/src/views/Register.vue @@ -466,13 +466,9 @@ async function handleInstallApp() { console.error('Erreur lors de l\'installation:', error) } } else if (isMobile.value) { - // Sur mobile sans beforeinstallprompt, afficher les instructions - const isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent) - if (isIOS) { - alert('Sur iOS :\n1. Appuyez sur le bouton de partage (□↑) en bas de l\'écran\n2. Faites défiler et sélectionnez "Sur l\'écran d\'accueil"\n3. Appuyez sur "Ajouter"') - } else { - alert('Sur Android :\n1. Appuyez sur le menu (⋮) en haut à droite\n2. Sélectionnez "Ajouter à l\'écran d\'accueil" ou "Installer l\'application"\n3. Confirmez l\'installation') - } + // Sur mobile sans beforeinstallprompt, on ne peut pas installer directement + // L'utilisateur devra utiliser le menu une fois connecté + console.log('Installation PWA non disponible sur ce navigateur mobile') } } diff --git a/frontend/src/views/UserProfile.vue b/frontend/src/views/UserProfile.vue index 9227755..cc06eea 100644 --- a/frontend/src/views/UserProfile.vue +++ b/frontend/src/views/UserProfile.vue @@ -123,8 +123,7 @@ import { useAuthStore } from '@/stores/auth' import { useToast } from 'vue-toastification' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { format, formatDistanceToNow } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatDateInFrenchTimezone, formatRelativeDateInFrenchTimezone } from '@/utils/dateUtils' import { User, ArrowLeft, ArrowRight, MessageSquare, Video, Image, Calendar, Activity } from 'lucide-vue-next' import LoadingLogo from '@/components/LoadingLogo.vue' @@ -140,12 +139,12 @@ const recentActivity = ref([]) function formatDate(date) { if (!date) return '' - return format(new Date(date), 'MMMM yyyy', { locale: fr }) + return formatDateInFrenchTimezone(date, 'MMMM yyyy') } function formatRelativeDate(date) { if (!date) return '' - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } function getActivityIcon(type) { diff --git a/frontend/src/views/VlogDetail.vue b/frontend/src/views/VlogDetail.vue index 0880a81..a59986d 100644 --- a/frontend/src/views/VlogDetail.vue +++ b/frontend/src/views/VlogDetail.vue @@ -228,8 +228,7 @@ import { useAuthStore } from '@/stores/auth' import { useToast } from 'vue-toastification' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { format, formatDistanceToNow } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatDateInFrenchTimezone, formatRelativeDateInFrenchTimezone } from '@/utils/dateUtils' import { ArrowLeft, User, @@ -269,11 +268,11 @@ const canEdit = computed(() => ) function formatDate(date) { - return format(new Date(date), 'dd MMMM yyyy', { locale: fr }) + return formatDateInFrenchTimezone(date, 'dd MMMM yyyy') } function formatRelativeDate(date) { - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } function formatDuration(seconds) { diff --git a/frontend/src/views/Vlogs.vue b/frontend/src/views/Vlogs.vue index f32b2db..1692482 100644 --- a/frontend/src/views/Vlogs.vue +++ b/frontend/src/views/Vlogs.vue @@ -268,8 +268,7 @@ import { useToast } from 'vue-toastification' import { useRouter } from 'vue-router' import axios from '@/utils/axios' import { getMediaUrl } from '@/utils/axios' -import { formatDistanceToNow } from 'date-fns' -import { fr } from 'date-fns/locale' +import { formatRelativeDateInFrenchTimezone } from '@/utils/dateUtils' import { Plus, Film, @@ -312,7 +311,7 @@ function formatDuration(seconds) { } function formatRelativeDate(date) { - return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr }) + return formatRelativeDateInFrenchTimezone(date) } function openVlog(vlog) { diff --git a/frontend/vite.config.js b/frontend/vite.config.js index ae7175d..ac5a3ef 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -179,6 +179,8 @@ module.exports = defineConfig(({ command, mode }) => { navigateFallback: null, skipWaiting: true, clientsClaim: true, + // Intégrer le service worker personnalisé + importScripts: ['/sw-custom.js'], runtimeCaching: [ { urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,