mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-14 02:38:58 +02:00
latest
This commit is contained in:
1 parent
e3fb29ab1d
commit
2485f71dd7
95 files changed
+41842
No files matched your search
@@ -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
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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'
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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'
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user