fix(date): correction on date utils
This commit is contained in:
@@ -67,8 +67,9 @@ if (!self.define) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
define(['./workbox-47da91e0'], (function (workbox) { 'use strict';
|
define(['./workbox-9be7f7ba'], (function (workbox) { 'use strict';
|
||||||
|
|
||||||
|
importScripts("/sw-custom.js");
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
workbox.clientsClaim();
|
workbox.clientsClaim();
|
||||||
|
|
||||||
@@ -80,14 +81,8 @@ define(['./workbox-47da91e0'], (function (workbox) { 'use strict';
|
|||||||
workbox.precacheAndRoute([{
|
workbox.precacheAndRoute([{
|
||||||
"url": "registerSW.js",
|
"url": "registerSW.js",
|
||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
}, {
|
|
||||||
"url": "index.html",
|
|
||||||
"revision": "0.abqp38bc5fg"
|
|
||||||
}], {});
|
}], {});
|
||||||
workbox.cleanupOutdatedCaches();
|
workbox.cleanupOutdatedCaches();
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
|
||||||
allowlist: [/^\/$/]
|
|
||||||
}));
|
|
||||||
workbox.registerRoute(/^https:\/\/fonts\.googleapis\.com\/.*/i, new workbox.CacheFirst({
|
workbox.registerRoute(/^https:\/\/fonts\.googleapis\.com\/.*/i, new workbox.CacheFirst({
|
||||||
"cacheName": "google-fonts-cache",
|
"cacheName": "google-fonts-cache",
|
||||||
plugins: [new workbox.ExpirationPlugin({
|
plugins: [new workbox.ExpirationPlugin({
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
4646
frontend/dev-dist/workbox-9be7f7ba.js
Normal file
4646
frontend/dev-dist/workbox-9be7f7ba.js
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/dev-dist/workbox-9be7f7ba.js.map
Normal file
1
frontend/dev-dist/workbox-9be7f7ba.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -40,18 +40,32 @@ self.addEventListener('message', (event) => {
|
|||||||
if (event.data && event.data.type === 'SHOW_NOTIFICATION') {
|
if (event.data && event.data.type === 'SHOW_NOTIFICATION') {
|
||||||
const { title, options } = event.data
|
const { title, options } = event.data
|
||||||
|
|
||||||
|
// Préparer les options de notification
|
||||||
|
const notificationOptions = {
|
||||||
|
icon: '/icon-192x192.png',
|
||||||
|
badge: '/icon-96x96.png',
|
||||||
|
tag: 'lediscord-notification',
|
||||||
|
requireInteraction: false,
|
||||||
|
data: {
|
||||||
|
link: options.link || options.data?.link || '/',
|
||||||
|
notificationId: options.data?.notificationId || options.notificationId
|
||||||
|
},
|
||||||
|
body: options.body || options.message || '',
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retirer vibrate si présent (iOS ne le supporte pas)
|
||||||
|
// Le client devrait déjà l'avoir retiré, mais on s'assure ici aussi
|
||||||
|
if (notificationOptions.vibrate) {
|
||||||
|
// Vérifier si on est sur iOS (approximatif via user agent du client)
|
||||||
|
// Note: dans le SW on n'a pas accès direct à navigator.userAgent
|
||||||
|
// mais on peut retirer vibrate de toute façon car iOS l'ignore
|
||||||
|
delete notificationOptions.vibrate
|
||||||
|
}
|
||||||
|
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
self.registration.showNotification(title, {
|
self.registration.showNotification(title, notificationOptions).catch(error => {
|
||||||
icon: '/icon-192x192.png',
|
console.error('Error showing notification in service worker:', error)
|
||||||
badge: '/icon-96x96.png',
|
|
||||||
tag: 'lediscord-notification',
|
|
||||||
requireInteraction: false,
|
|
||||||
vibrate: [200, 100, 200],
|
|
||||||
data: {
|
|
||||||
link: options.link || '/',
|
|
||||||
notificationId: options.data?.notificationId
|
|
||||||
},
|
|
||||||
...options
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -90,8 +104,15 @@ self.addEventListener('push', (event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retirer vibrate pour compatibilité iOS
|
||||||
|
if (notificationData.vibrate) {
|
||||||
|
delete notificationData.vibrate
|
||||||
|
}
|
||||||
|
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
self.registration.showNotification(notificationData.title, notificationData)
|
self.registration.showNotification(notificationData.title, notificationData).catch(error => {
|
||||||
|
console.error('Error showing push notification:', error)
|
||||||
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -434,12 +434,24 @@ onMounted(async () => {
|
|||||||
await authStore.fetchUnreadCount()
|
await authStore.fetchUnreadCount()
|
||||||
|
|
||||||
// Démarrer le polling des notifications
|
// Démarrer le polling des notifications
|
||||||
const notificationService = (await import('@/services/notificationService')).default
|
try {
|
||||||
notificationService.startPolling()
|
const notificationService = (await import('@/services/notificationService')).default
|
||||||
notificationService.setupServiceWorkerListener()
|
notificationService.startPolling()
|
||||||
|
notificationService.setupServiceWorkerListener()
|
||||||
|
|
||||||
// Demander la permission pour les notifications push
|
// Demander la permission pour les notifications push
|
||||||
await notificationService.requestNotificationPermission()
|
// Sur iOS, attendre un peu pour s'assurer que la PWA est bien détectée
|
||||||
|
if (typeof notificationService.isIOS === 'function' && notificationService.isIOS()) {
|
||||||
|
// Attendre que la page soit complètement chargée
|
||||||
|
setTimeout(async () => {
|
||||||
|
await notificationService.requestNotificationPermission()
|
||||||
|
}, 1000)
|
||||||
|
} else {
|
||||||
|
await notificationService.requestNotificationPermission()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors du chargement du service de notifications:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifier si PWA est installée
|
// Vérifier si PWA est installée
|
||||||
@@ -458,7 +470,11 @@ onBeforeUnmount(async () => {
|
|||||||
window.removeEventListener('resize', checkIfMobile)
|
window.removeEventListener('resize', checkIfMobile)
|
||||||
|
|
||||||
// Arrêter le polling des notifications
|
// Arrêter le polling des notifications
|
||||||
const notificationService = (await import('@/services/notificationService')).default
|
try {
|
||||||
notificationService.stopPolling()
|
const notificationService = (await import('@/services/notificationService')).default
|
||||||
|
notificationService.stopPolling()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erreur lors de l\'arrêt du service de notifications:', error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -37,6 +37,29 @@ app.use(Toast, toastOptions)
|
|||||||
// Maintenant installer le router
|
// Maintenant installer le router
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
|
// Handler d'erreur global pour capturer les erreurs non catchées
|
||||||
|
window.addEventListener('error', (event) => {
|
||||||
|
console.error('❌ Erreur JavaScript globale:', {
|
||||||
|
message: event.message,
|
||||||
|
filename: event.filename,
|
||||||
|
lineno: event.lineno,
|
||||||
|
colno: event.colno,
|
||||||
|
error: event.error,
|
||||||
|
stack: event.error?.stack
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handler pour les promesses rejetées non catchées
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
console.error('❌ Promesse rejetée non catchée:', {
|
||||||
|
reason: event.reason,
|
||||||
|
promise: event.promise,
|
||||||
|
stack: event.reason?.stack
|
||||||
|
})
|
||||||
|
// Empêcher le message d'erreur par défaut dans la console
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
// Attendre que le router soit prêt avant de monter l'app
|
// Attendre que le router soit prêt avant de monter l'app
|
||||||
router.isReady().then(() => {
|
router.isReady().then(() => {
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -7,6 +7,47 @@ class NotificationService {
|
|||||||
this.isPolling = false
|
this.isPolling = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Détecter iOS
|
||||||
|
isIOS() {
|
||||||
|
return /iPhone|iPad|iPod/i.test(navigator.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si la PWA est installée (nécessaire pour iOS)
|
||||||
|
isPWAInstalled() {
|
||||||
|
return window.matchMedia('(display-mode: standalone)').matches ||
|
||||||
|
window.navigator.standalone === true ||
|
||||||
|
document.referrer.includes('android-app://')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si les notifications sont supportées sur cette plateforme
|
||||||
|
isNotificationSupported() {
|
||||||
|
if (!('Notification' in window)) {
|
||||||
|
console.warn('Notifications API not supported')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sur iOS, les notifications push ne fonctionnent que si la PWA est installée (iOS 16.4+)
|
||||||
|
if (this.isIOS()) {
|
||||||
|
if (!this.isPWAInstalled()) {
|
||||||
|
console.warn('iOS: Notifications push require PWA to be installed (added to home screen)')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier la version iOS (approximatif via user agent)
|
||||||
|
const iosVersion = navigator.userAgent.match(/OS (\d+)_(\d+)/)
|
||||||
|
if (iosVersion) {
|
||||||
|
const major = parseInt(iosVersion[1], 10)
|
||||||
|
const minor = parseInt(iosVersion[2], 10)
|
||||||
|
if (major < 16 || (major === 16 && minor < 4)) {
|
||||||
|
console.warn('iOS: Push notifications require iOS 16.4 or later')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
startPolling() {
|
startPolling() {
|
||||||
if (this.isPolling) return
|
if (this.isPolling) return
|
||||||
|
|
||||||
@@ -89,28 +130,74 @@ class NotificationService {
|
|||||||
|
|
||||||
// Gestion des notifications push PWA
|
// Gestion des notifications push PWA
|
||||||
async requestNotificationPermission() {
|
async requestNotificationPermission() {
|
||||||
if (!('Notification' in window)) {
|
if (!this.isNotificationSupported()) {
|
||||||
console.log('Ce navigateur ne supporte pas les notifications')
|
console.log('Notifications not supported on this platform')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Notification.permission === 'granted') {
|
if (Notification.permission === 'granted') {
|
||||||
|
console.log('Notification permission already granted')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Notification.permission !== 'denied') {
|
if (Notification.permission === 'denied') {
|
||||||
const permission = await Notification.requestPermission()
|
console.warn('Notification permission denied by user')
|
||||||
return permission === 'granted'
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
// Sur iOS, s'assurer que la PWA est installée avant de demander
|
||||||
|
if (this.isIOS() && !this.isPWAInstalled()) {
|
||||||
|
console.warn('iOS: Cannot request notification permission - PWA must be installed first')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const permission = await Notification.requestPermission()
|
||||||
|
const granted = permission === 'granted'
|
||||||
|
|
||||||
|
if (granted) {
|
||||||
|
console.log('Notification permission granted')
|
||||||
|
} else {
|
||||||
|
console.warn('Notification permission denied:', permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
return granted
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error requesting notification permission:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async showPushNotification(title, options = {}) {
|
async showPushNotification(title, options = {}) {
|
||||||
if (!('Notification' in window)) return
|
if (!this.isNotificationSupported()) {
|
||||||
|
console.warn('Cannot show notification - not supported on this platform')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const hasPermission = await this.requestNotificationPermission()
|
const hasPermission = await this.requestNotificationPermission()
|
||||||
if (!hasPermission) return
|
if (!hasPermission) {
|
||||||
|
console.warn('Cannot show notification - permission not granted')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Préparer les options de notification (iOS ne supporte pas vibrate)
|
||||||
|
const notificationOptions = {
|
||||||
|
body: options.body || options.message || '',
|
||||||
|
icon: '/icon-192x192.png',
|
||||||
|
badge: '/icon-96x96.png',
|
||||||
|
tag: 'lediscord-notification',
|
||||||
|
requireInteraction: false,
|
||||||
|
data: {
|
||||||
|
link: options.link || '/',
|
||||||
|
notificationId: options.data?.notificationId
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retirer vibrate sur iOS (non supporté)
|
||||||
|
if (!this.isIOS()) {
|
||||||
|
notificationOptions.vibrate = [200, 100, 200]
|
||||||
|
}
|
||||||
|
|
||||||
// Toujours utiliser le service worker pour les notifications push
|
// Toujours utiliser le service worker pour les notifications push
|
||||||
// Cela permet aux notifications de fonctionner même quand l'app est fermée
|
// Cela permet aux notifications de fonctionner même quand l'app est fermée
|
||||||
@@ -118,74 +205,58 @@ class NotificationService {
|
|||||||
try {
|
try {
|
||||||
const registration = await navigator.serviceWorker.ready
|
const registration = await navigator.serviceWorker.ready
|
||||||
|
|
||||||
|
if (!registration.active) {
|
||||||
|
console.warn('Service worker not active, using fallback')
|
||||||
|
throw new Error('Service worker not active')
|
||||||
|
}
|
||||||
|
|
||||||
// Envoyer un message au service worker pour afficher la notification
|
// Envoyer un message au service worker pour afficher la notification
|
||||||
// Cela permet de gérer les clics correctement
|
registration.active.postMessage({
|
||||||
registration.active?.postMessage({
|
|
||||||
type: 'SHOW_NOTIFICATION',
|
type: 'SHOW_NOTIFICATION',
|
||||||
title,
|
title,
|
||||||
options: {
|
options: notificationOptions
|
||||||
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
|
// Aussi utiliser l'API directe du service worker
|
||||||
await registration.showNotification(title, {
|
await registration.showNotification(title, notificationOptions)
|
||||||
icon: '/icon-192x192.png',
|
|
||||||
badge: '/icon-96x96.png',
|
console.log('Notification shown via service worker')
|
||||||
tag: 'lediscord-notification',
|
return null
|
||||||
requireInteraction: false,
|
|
||||||
vibrate: [200, 100, 200],
|
|
||||||
data: {
|
|
||||||
link: options.link || '/',
|
|
||||||
notificationId: options.data?.notificationId
|
|
||||||
},
|
|
||||||
body: options.body || options.message || '',
|
|
||||||
...options
|
|
||||||
})
|
|
||||||
return
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error showing notification via service worker:', error)
|
console.error('Error showing notification via service worker:', error)
|
||||||
|
// Continuer avec le fallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: notification native du navigateur (seulement si le SW n'est pas disponible)
|
// Fallback: notification native du navigateur (seulement si le SW n'est pas disponible)
|
||||||
const notification = new Notification(title, {
|
try {
|
||||||
icon: '/icon-192x192.png',
|
const notification = new Notification(title, notificationOptions)
|
||||||
badge: '/icon-96x96.png',
|
|
||||||
tag: 'lediscord-notification',
|
|
||||||
requireInteraction: false,
|
|
||||||
body: options.body || options.message || '',
|
|
||||||
...options
|
|
||||||
})
|
|
||||||
|
|
||||||
notification.onclick = () => {
|
notification.onclick = () => {
|
||||||
window.focus()
|
window.focus()
|
||||||
notification.close()
|
notification.close()
|
||||||
|
|
||||||
if (options.link) {
|
if (options.link) {
|
||||||
// Utiliser le router si disponible
|
// Utiliser le router si disponible
|
||||||
if (window.location.pathname !== options.link) {
|
if (window.location.pathname !== options.link) {
|
||||||
window.location.href = options.link
|
window.location.href = options.link
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fermer automatiquement après 5 secondes (sauf sur iOS où c'est géré par le système)
|
||||||
|
if (!this.isIOS()) {
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.close()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Notification shown via native API')
|
||||||
|
return notification
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error showing notification:', error)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fermer automatiquement après 5 secondes
|
|
||||||
setTimeout(() => {
|
|
||||||
notification.close()
|
|
||||||
}, 5000)
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Écouter les messages du service worker pour les notifications push
|
// Écouter les messages du service worker pour les notifications push
|
||||||
|
|||||||
@@ -53,11 +53,19 @@ instance.interceptors.request.use(
|
|||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers.Authorization = `Bearer ${token}`
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
}
|
// Log détaillé en développement
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
// Log des requêtes en développement
|
console.log(`📤 Requête ${config.method?.toUpperCase()} vers: ${config.url}`, {
|
||||||
if (import.meta.env.DEV) {
|
hasToken: !!token,
|
||||||
console.log(`📤 Requête ${config.method?.toUpperCase()} vers: ${config.url}`)
|
tokenLength: token.length,
|
||||||
|
tokenPreview: token.substring(0, 20) + '...'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Log si pas de token
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.warn(`⚠️ Requête ${config.method?.toUpperCase()} vers: ${config.url} - Pas de token`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config
|
return config
|
||||||
@@ -86,9 +94,23 @@ instance.interceptors.response.use(
|
|||||||
statusText: error.response?.statusText,
|
statusText: error.response?.statusText,
|
||||||
url: error.config?.url,
|
url: error.config?.url,
|
||||||
method: error.config?.method,
|
method: error.config?.method,
|
||||||
data: error.response?.data
|
data: error.response?.data,
|
||||||
|
headers: error.response?.headers,
|
||||||
|
requestHeaders: error.config?.headers
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Log supplémentaire pour les erreurs 401/403
|
||||||
|
if (error.response?.status === 401 || error.response?.status === 403) {
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
console.error('🔍 Diagnostic erreur auth:', {
|
||||||
|
hasToken: !!token,
|
||||||
|
tokenLength: token?.length,
|
||||||
|
tokenPreview: token ? token.substring(0, 20) + '...' : null,
|
||||||
|
url: error.config?.url,
|
||||||
|
method: error.config?.method
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
// Ne pas rediriger si on est déjà sur une page d'auth
|
// Ne pas rediriger si on est déjà sur une page d'auth
|
||||||
const currentRoute = router.currentRoute.value
|
const currentRoute = router.currentRoute.value
|
||||||
|
|||||||
@@ -25,11 +25,23 @@ export function formatDateInFrenchTimezone(date, formatStr = 'dd MMM à HH:mm')
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Formate une date relative dans le fuseau horaire français
|
* Formate une date relative dans le fuseau horaire français
|
||||||
|
* Note: On s'assure que la date est correctement parsée comme UTC
|
||||||
*/
|
*/
|
||||||
export function formatRelativeDateInFrenchTimezone(date) {
|
export function formatRelativeDateInFrenchTimezone(date) {
|
||||||
if (!date) return ''
|
if (!date) return ''
|
||||||
const frenchDate = toFrenchTimezone(date)
|
|
||||||
return formatDistanceToNow(frenchDate, { addSuffix: true, locale: fr })
|
// Convertir la date en objet Date si ce n'est pas déjà le cas
|
||||||
|
let dateObj = date instanceof Date ? date : new Date(date)
|
||||||
|
|
||||||
|
// Si la date est une string sans "Z" à la fin, elle est interprétée comme locale
|
||||||
|
// On doit s'assurer qu'elle est traitée comme UTC
|
||||||
|
if (typeof date === 'string' && !date.endsWith('Z') && !date.includes('+') && !date.includes('-', 10)) {
|
||||||
|
// Si c'est une date ISO sans timezone, on l'interprète comme UTC
|
||||||
|
dateObj = new Date(date + 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculer la distance depuis maintenant
|
||||||
|
return formatDistanceToNow(dateObj, { addSuffix: true, locale: fr })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ import { useToast } from 'vue-toastification'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import axios from '@/utils/axios'
|
import axios from '@/utils/axios'
|
||||||
import { getMediaUrl } from '@/utils/axios'
|
import { getMediaUrl } from '@/utils/axios'
|
||||||
import { formatRelativeDateInFrenchTimezone, formatShortDateInFrenchTimezone } from '@/utils/dateUtils'
|
import { formatRelativeDateInFrenchTimezone, formatShortDateInFrenchTimezone, convertFrenchTimezoneToUTC } from '@/utils/dateUtils'
|
||||||
import {
|
import {
|
||||||
Plus,
|
Plus,
|
||||||
Calendar,
|
Calendar,
|
||||||
@@ -467,7 +467,13 @@ async function quickParticipation(eventId, status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createEvent() {
|
async function createEvent() {
|
||||||
if (!newEvent.value.title || !newEvent.value.date) return
|
console.log('🔵 createEvent appelée')
|
||||||
|
console.log('🔵 newEvent.value:', newEvent.value)
|
||||||
|
|
||||||
|
if (!newEvent.value.title || !newEvent.value.date) {
|
||||||
|
console.warn('⚠️ Validation échouée: titre ou date manquant')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Vérifier que des invités sont sélectionnés pour les événements privés
|
// Vérifier que des invités sont sélectionnés pour les événements privés
|
||||||
if (newEvent.value.is_private && (!newEvent.value.invited_user_ids || newEvent.value.invited_user_ids.length === 0)) {
|
if (newEvent.value.is_private && (!newEvent.value.invited_user_ids || newEvent.value.invited_user_ids.length === 0)) {
|
||||||
@@ -476,6 +482,8 @@ async function createEvent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
creating.value = true
|
creating.value = true
|
||||||
|
console.log('🔵 creating.value = true')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const eventData = {
|
const eventData = {
|
||||||
title: newEvent.value.title,
|
title: newEvent.value.title,
|
||||||
@@ -487,7 +495,12 @@ async function createEvent() {
|
|||||||
invited_user_ids: newEvent.value.is_private ? newEvent.value.invited_user_ids : null
|
invited_user_ids: newEvent.value.is_private ? newEvent.value.invited_user_ids : null
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.post('/api/events', eventData)
|
console.log('🔵 eventData préparé:', eventData)
|
||||||
|
console.log('🔵 Envoi de la requête axios.post...')
|
||||||
|
|
||||||
|
const response = await axios.post('/api/events', eventData)
|
||||||
|
|
||||||
|
console.log('✅ Réponse reçue:', response)
|
||||||
|
|
||||||
// Refresh events list
|
// Refresh events list
|
||||||
await fetchEvents()
|
await fetchEvents()
|
||||||
@@ -496,9 +509,18 @@ async function createEvent() {
|
|||||||
resetForm()
|
resetForm()
|
||||||
toast.success(newEvent.value.is_private ? 'Événement privé créé avec succès' : 'Événement créé avec succès')
|
toast.success(newEvent.value.is_private ? 'Événement privé créé avec succès' : 'Événement créé avec succès')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('❌ Erreur dans createEvent:', error)
|
||||||
|
console.error('❌ Détails de l\'erreur:', {
|
||||||
|
message: error.message,
|
||||||
|
response: error.response,
|
||||||
|
request: error.request,
|
||||||
|
config: error.config
|
||||||
|
})
|
||||||
toast.error('Erreur lors de la création de l\'événement')
|
toast.error('Erreur lors de la création de l\'événement')
|
||||||
|
} finally {
|
||||||
|
creating.value = false
|
||||||
|
console.log('🔵 creating.value = false')
|
||||||
}
|
}
|
||||||
creating.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
|
|||||||
@@ -284,7 +284,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, watch, nextTick } from 'vue'
|
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
@@ -320,6 +320,7 @@ const showImageUpload = ref(false)
|
|||||||
|
|
||||||
const offset = ref(0)
|
const offset = ref(0)
|
||||||
const hasMorePosts = ref(true)
|
const hasMorePosts = ref(true)
|
||||||
|
const dateRefreshInterval = ref(null)
|
||||||
|
|
||||||
const newPost = ref({
|
const newPost = ref({
|
||||||
content: '',
|
content: '',
|
||||||
@@ -327,7 +328,12 @@ const newPost = ref({
|
|||||||
mentioned_user_ids: []
|
mentioned_user_ids: []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Force refresh pour les dates relatives
|
||||||
|
const dateRefreshKey = ref(0)
|
||||||
|
|
||||||
function formatRelativeDate(date) {
|
function formatRelativeDate(date) {
|
||||||
|
// Utiliser dateRefreshKey pour forcer le recalcul
|
||||||
|
dateRefreshKey.value
|
||||||
return formatRelativeDateInFrenchTimezone(date)
|
return formatRelativeDateInFrenchTimezone(date)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,6 +364,14 @@ async function createPost() {
|
|||||||
// Add new post to the beginning of the list
|
// Add new post to the beginning of the list
|
||||||
posts.value.unshift(response.data)
|
posts.value.unshift(response.data)
|
||||||
|
|
||||||
|
// Forcer le rafraîchissement de la date immédiatement
|
||||||
|
dateRefreshKey.value++
|
||||||
|
|
||||||
|
// Rafraîchir à nouveau après 1 seconde pour s'assurer que ça s'affiche correctement
|
||||||
|
setTimeout(() => {
|
||||||
|
dateRefreshKey.value++
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
// Reset form
|
// Reset form
|
||||||
newPost.value = {
|
newPost.value = {
|
||||||
content: '',
|
content: '',
|
||||||
@@ -471,13 +485,35 @@ async function fetchPosts() {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`/api/posts?limit=10&offset=${offset.value}`)
|
const response = await axios.get(`/api/posts?limit=10&offset=${offset.value}`)
|
||||||
|
|
||||||
|
// S'assurer que les dates sont correctement parsées comme UTC
|
||||||
|
const postsData = response.data.map(post => {
|
||||||
|
// Si la date created_at est une string sans timezone, l'interpréter comme UTC
|
||||||
|
if (post.created_at && typeof post.created_at === 'string' && !post.created_at.endsWith('Z') && !post.created_at.includes('+') && !post.created_at.includes('-', 10)) {
|
||||||
|
post.created_at = post.created_at + 'Z'
|
||||||
|
}
|
||||||
|
// Même chose pour les commentaires
|
||||||
|
if (post.comments) {
|
||||||
|
post.comments = post.comments.map(comment => {
|
||||||
|
if (comment.created_at && typeof comment.created_at === 'string' && !comment.created_at.endsWith('Z') && !comment.created_at.includes('+') && !comment.created_at.includes('-', 10)) {
|
||||||
|
comment.created_at = comment.created_at + 'Z'
|
||||||
|
}
|
||||||
|
return comment
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return post
|
||||||
|
})
|
||||||
|
|
||||||
if (offset.value === 0) {
|
if (offset.value === 0) {
|
||||||
posts.value = response.data
|
posts.value = postsData
|
||||||
} else {
|
} else {
|
||||||
posts.value.push(...response.data)
|
posts.value.push(...postsData)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMorePosts.value = response.data.length === 10
|
hasMorePosts.value = response.data.length === 10
|
||||||
|
|
||||||
|
// Forcer le rafraîchissement des dates après le chargement
|
||||||
|
dateRefreshKey.value++
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Erreur lors du chargement des publications')
|
toast.error('Erreur lors du chargement des publications')
|
||||||
}
|
}
|
||||||
@@ -549,5 +585,16 @@ onMounted(async () => {
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
await scrollToPost(parseInt(route.query.highlight))
|
await scrollToPost(parseInt(route.query.highlight))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rafraîchir les dates relatives toutes les 30 secondes
|
||||||
|
dateRefreshInterval.value = setInterval(() => {
|
||||||
|
dateRefreshKey.value++
|
||||||
|
}, 30000)
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (dateRefreshInterval.value) {
|
||||||
|
clearInterval(dateRefreshInterval.value)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ module.exports = defineConfig(({ command, mode }) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
urlPattern: /^https?:\/\/.*\/uploads\/.*/i,
|
urlPattern: /^https?:\/\/.*\/uploads\/.*/i,
|
||||||
handler: 'CacheFirst',
|
handler: 'StaleWhileRevalidate',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'uploads-cache',
|
cacheName: 'uploads-cache',
|
||||||
expiration: {
|
expiration: {
|
||||||
|
|||||||
Reference in New Issue
Block a user