This commit is contained in:
LogiFlow committed 2025-08-11 14:43:48 +00:00
1 parent e3fb29ab1d
commit 2485f71dd7
95 files changed
+41842

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}
+191
View File
@@ -0,0 +1,191 @@
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }
+28
View File
@@ -0,0 +1,28 @@
import { useQuery } from "@tanstack/react-query";
export function useAuth() {
const { data: user, isLoading, error } = useQuery({
queryKey: ["/api/user"],
retry: (failureCount, error: any) => {
// Ne pas retry si c'est une erreur d'authentification
if (error?.message?.includes('401') || error?.message?.includes('Unauthorized')) {
return false;
}
return failureCount < 2;
},
refetchInterval: false,
refetchOnWindowFocus: false,
refetchOnMount: true, // Permettre le rechargement au montage
refetchOnReconnect: false,
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes
enabled: true,
});
return {
user: user || null,
isLoading,
isAuthenticated: !!user,
error,
};
}
+70
View File
@@ -0,0 +1,70 @@
import { useState, useEffect } from 'react';
// Hook d'authentification spécifique pour la production
// Utilise fetch direct sans React Query pour éviter les problèmes de cache
export function useAuthProduction() {
const [user, setUser] = useState<any>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<any>(null);
const [refreshTrigger, setRefreshTrigger] = useState(0);
const checkAuth = async () => {
try {
setIsLoading(true);
setError(null);
console.log('🔄 Production auth check starting...');
const response = await fetch('/api/user', {
credentials: 'include',
cache: 'no-cache',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
console.log('🔄 Production auth response:', response.status);
if (response.ok) {
const userData = await response.json();
console.log('✅ Production auth success:', { username: userData?.username, id: userData?.id });
setUser(userData);
setError(null);
} else if (response.status === 401) {
console.log('❌ Production auth 401 - user not authenticated');
setUser(null);
setError(null);
} else {
throw new Error(`Auth failed: ${response.status}`);
}
} catch (err) {
console.error('❌ Production auth error:', err);
setError(err);
setUser(null);
} finally {
setIsLoading(false);
console.log('🔄 Production auth check completed');
}
};
// Authentification au montage et lors des triggers
useEffect(() => {
checkAuth();
}, [refreshTrigger]);
// Fonction pour rafraîchir l'authentification
const refreshAuth = () => {
console.log('🔄 RefreshAuth called for production');
setRefreshTrigger(prev => prev + 1);
};
return {
user,
isLoading,
isAuthenticated: !!user,
error,
refreshAuth,
environment: 'production'
};
}
+63
View File
@@ -0,0 +1,63 @@
import { useState, useEffect } from 'react';
// Hook d'authentification simplifié qui fonctionne dans tous les environnements
export function useAuthSimple() {
const [user, setUser] = useState<any>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<any>(null);
const [refreshCounter, setRefreshCounter] = useState(0);
// Fonction pour vérifier l'authentification
const checkAuth = async () => {
try {
setIsLoading(true);
const response = await fetch('/api/user', {
credentials: 'include',
cache: 'no-cache',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
if (response.ok) {
const userData = await response.json();
console.log('✅ Auth check successful:', { username: userData?.username, id: userData?.id });
setUser(userData);
setError(null);
} else if (response.status === 401) {
console.log('❌ Auth check 401 - user not authenticated');
setUser(null);
setError(null);
} else {
throw new Error(`Auth failed: ${response.status}`);
}
} catch (err) {
console.error('Auth check error:', err);
setError(err);
setUser(null);
} finally {
setIsLoading(false);
}
};
// Fonction pour forcer un refresh de l'authentification
const refreshAuth = () => {
console.log('🔄 Refreshing authentication...');
setRefreshCounter(prev => prev + 1);
};
// Vérifier l'authentification au montage et lors des refresh
useEffect(() => {
checkAuth();
}, [refreshCounter]);
return {
user,
isLoading,
error,
isAuthenticated: !!user,
refreshAuth
};
}
+205
View File
@@ -0,0 +1,205 @@
import { useState, useEffect } from 'react';
import { useQuery } from "@tanstack/react-query";
// Hook d'authentification unifié qui s'adapte automatiquement
// En production utilise fetch direct, en développement utilise React Query
export function useAuthUnified() {
// Détection d'environnement plus robuste
const isDevelopment = typeof window !== 'undefined' &&
(window.location.hostname === 'localhost' ||
window.location.hostname.includes('replit.dev')) &&
import.meta.env.DEV === true;
// Debug logging pour comprendre l'environnement
console.log('🔍 Auth Environment Debug:', {
hostname: typeof window !== 'undefined' ? window.location.hostname : 'N/A',
isDev: import.meta.env.DEV,
environment: isDevelopment ? 'development' : 'production'
});
// État pour la version production (fetch direct)
const [productionUser, setProductionUser] = useState<any>(null);
const [productionLoading, setProductionLoading] = useState(true);
const [productionError, setProductionError] = useState<any>(null);
const [refreshTrigger, setRefreshTrigger] = useState(0);
// Hook React Query pour le développement
const developmentQuery = useQuery({
queryKey: ["/api/user"],
retry: (failureCount, error: any) => {
if (error?.message?.includes('401') || error?.message?.includes('Unauthorized')) {
return false;
}
return failureCount < 2;
},
refetchInterval: false,
refetchOnWindowFocus: false,
refetchOnMount: true,
refetchOnReconnect: false,
staleTime: 10 * 60 * 1000, // 10 minutes de cache pour l'auth
gcTime: 15 * 60 * 1000, // 15 minutes
enabled: isDevelopment, // Seulement en développement
});
// Fonction pour rafraîchir l'authentification
const refreshAuth = () => {
console.log('🔄 RefreshAuth called, isDevelopment:', isDevelopment);
if (!isDevelopment) {
console.log('🔄 Triggering production auth refresh');
setRefreshTrigger(prev => {
const newValue = prev + 1;
console.log('🔄 Production refresh trigger updated:', prev, '->', newValue);
return newValue;
});
} else {
console.log('🔄 Development mode - using React Query refresh');
developmentQuery.refetch();
}
};
// Fonction pour rafraîchir de manière synchrone (pour après login)
const forceAuthRefresh = async () => {
console.log('🔄 ForceAuthRefresh called, isDevelopment:', isDevelopment);
if (!isDevelopment) {
// En production, faire un fetch immédiat et forcer un re-render
try {
console.log('🔄 Production force refresh - fetching user data');
setProductionLoading(true);
const response = await fetch('/api/user', {
credentials: 'include',
cache: 'no-cache',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
if (response.ok) {
const userData = await response.json();
console.log('✅ Production force refresh success:', { username: userData?.username, id: userData?.id });
setProductionUser(userData);
setProductionError(null);
setProductionLoading(false);
// Forcer un trigger de refresh pour déclencher les re-renders
setRefreshTrigger(prev => prev + 1);
return userData;
} else {
console.log('❌ Production force refresh failed:', response.status);
setProductionUser(null);
setProductionError(null);
setProductionLoading(false);
return null;
}
} catch (error) {
console.error('❌ Production force refresh error:', error);
setProductionError(error);
setProductionUser(null);
setProductionLoading(false);
return null;
}
} else {
// En développement, forcer un refetch avec invalidation du cache
console.log('🔄 Development mode - invalidating cache and refetching');
queryClient.invalidateQueries({ queryKey: ['/api/user'] });
// Attendre un court délai pour la propagation
await new Promise(resolve => setTimeout(resolve, 50));
const result = await developmentQuery.refetch();
console.log('🔄 Development refetch result:', {
success: result.isSuccess,
hasData: !!result.data,
userId: result.data?.id
});
return result.data;
}
};
// Authentification production (fetch direct)
useEffect(() => {
if (isDevelopment) return; // Ne pas exécuter en développement
let isMounted = true;
const checkAuth = async () => {
try {
console.log('🔄 Production auth check starting, refreshTrigger:', refreshTrigger);
setProductionLoading(true);
const response = await fetch('/api/user', {
credentials: 'include',
cache: 'no-cache',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
});
console.log('🔄 Production auth response:', response.status);
if (!isMounted) return;
if (response.ok) {
const userData = await response.json();
console.log('✅ Production auth success:', { username: userData?.username, id: userData?.id });
if (isMounted) {
setProductionUser(userData);
setProductionError(null);
}
} else if (response.status === 401) {
console.log('❌ Production auth 401 - user not authenticated');
if (isMounted) {
setProductionUser(null);
setProductionError(null);
}
} else {
throw new Error(`Auth failed: ${response.status}`);
}
} catch (err) {
console.error('Production auth error:', err);
if (isMounted) {
setProductionError(err);
setProductionUser(null);
}
} finally {
if (isMounted) {
console.log('🔄 Production auth check complete, loading set to false');
setProductionLoading(false);
}
}
};
checkAuth();
return () => {
isMounted = false;
};
}, [isDevelopment, refreshTrigger]); // Ajout du refreshTrigger
// Retourner les bonnes données selon l'environnement
if (isDevelopment) {
return {
user: developmentQuery.data || null,
isLoading: developmentQuery.isLoading,
isAuthenticated: !!developmentQuery.data,
error: developmentQuery.error,
refreshAuth: refreshAuth,
forceAuthRefresh: forceAuthRefresh,
environment: 'development'
};
} else {
return {
user: productionUser,
isLoading: productionLoading,
isAuthenticated: !!productionUser,
error: productionError,
refreshAuth: refreshAuth,
forceAuthRefresh: forceAuthRefresh,
environment: 'production'
};
}
}