import { useQuery } from "@tanstack/react-query"; import { DateTime, Duration } from "luxon"; const BASE_URL = import.meta.env.VITE_API_URL; const makePostRequest = async ( url: string, body: object, error = "Une erreur est survenue", method = "POST" ) => { const response = await fetch(BASE_URL + url, { method, headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(body), credentials: "include", // Include cookies for authentication }); const data = await response.json(); if (!response.ok) throw new Error(data.error || error); return data?.data || data; }; // TODO: Use swr or react-query for caching and revalidation // TODO: Cache all to localStorage or IndexedDB for offline support const makeRequest = async (url: string, error = "Une erreur est survenue") => { const response = await fetch(BASE_URL + url, { credentials: "include" }); const data = await response.json(); if (!response.ok) throw new Error(data.error || error); return data?.data || data; }; /** * === AUTH API === */ export const requestLogin = async (email: string, token: string) => { return makePostRequest( "/auth/request", { email, token }, "Échec de la demande de connexion" ); }; export const verifyOtp = async ({ otpCode, email, }: { otpCode: string; email: string; }) => { return makePostRequest( "/auth/verify", { email, code: otpCode }, "Code de vérification invalide" ); }; export const getClasses = async () => { try { const res = await fetch("/classes.json"); return res.json(); } catch (error) { console.error("Error fetching classes:", error); return []; } }; export const registerUser = async ( name: string, className: string, token: string ) => { return makePostRequest( "/auth/register", { name, className, token }, "Échec de l'inscription" ); }; export const getNames = async (className: string) => { return makeRequest( `/auth/autocomplete?className=${encodeURIComponent(className)}`, "Échec de la récupération des noms" ); }; /** * === USER API === */ const fetchUser = async () => { return makeRequest( "/users/@me", "Échec de la récupération des informations utilisateur" ); }; const defaultUser = { id: 0, firstName: "", lastName: "", fullName: "", email: "", className: "", preferences: [] as UserPreferences, }; export type User = typeof defaultUser; export const AUTH_ERROR = "Unauthorized access"; export const useUser = () => { const { data, ...props } = useQuery({ queryKey: ["user"], queryFn: fetchUser, staleTime: Duration.fromObject({ minutes: 5, // 5 minutes }).toMillis(), gcTime: Duration.fromObject({ days: 3, // 3 days }).toMillis(), }); return { user: (data ? Object.assign(defaultUser, data) : defaultUser) as User, ...props, }; }; export const logout = async () => { return makePostRequest("/auth/logout", {}, "Échec de la déconnexion", "POST"); }; export const updateUserPreferences = async ( preferences: { name: string; emoji: string; color: string }[] ) => { return makePostRequest( "/users/@me", { preferences }, "Échec de la mise à jour des préférences utilisateur", "POST" ); }; interface UserPreference { name: string; emoji: string; color: string; } export type UserPreferences = UserPreference[]; /** * === COLLES API === */ export const getColles = async (startDate: DateTime) => { return makeRequest( `/colles?startDate=${startDate.toISODate()}`, "Échec de la récupération des colles" ); }; export interface Colle { id: number; date: string; // ISO date string subject: { id: number; name: string; }; examiner: { id: number; name: string; }; room: { id: number; name: string; }; student: User; grade?: number; // Nullable grade bjsecret?: string; // Optional field bjid?: string; // Optional field content?: string; // Optional field comment?: string; // Optional field attachments?: Attachment[]; // Optional field, array of attachment URLs } interface CollePayload { classColles: Colle[]; studentColles: Colle[]; favoriteColles: Colle[]; healthyUntil: Date; lastSync: Date; } export const useColles = (startDate: DateTime) => { // Check if the start date is the current week // This is used to determine if the data is "actual" or not const isActual = DateTime.now() .startOf("week") .equals(startDate.startOf("week")); const options = isActual ? { refetchOnWindowFocus: true, refetchOnReconnect: true, staleTime: 0, gcTime: 0, } : { staleTime: Duration.fromObject({ hours: 1, // 1 hour }).toMillis(), gcTime: Duration.fromObject({ days: 3, // 3 days }).toMillis(), }; const { data, ...props } = useQuery({ queryKey: ["colles", startDate.toISODate()], queryFn: () => getColles(startDate), ...options, }); const mergedData = Object.assign( { classColles: [], studentColles: [], favoriteColles: [], healthyUntil: new Date(0), lastSync: new Date(0), }, data || {} ) as CollePayload; return { ...mergedData, ...props, }; }; const fetchColle = async (id: number) => { return makeRequest(`/colles/${id}`, "Échec de la récupération de la colle"); }; const defaultColle = { id: 0, date: "", subject: { id: 0, name: "", }, examiner: { id: 0, name: "", }, room: { id: 0, name: "", }, student: defaultUser, attachments: [], }; export const useColle = (id: number) => { const { data, ...props } = useQuery({ queryKey: ["colle", id], queryFn: () => fetchColle(id), staleTime: Duration.fromObject({ seconds: 30, // 30 seconds }).toMillis(), gcTime: Duration.fromObject({ days: 3, // 3 days }).toMillis(), }); return { colle: (data || defaultColle) as Colle, ...props, }; }; export const refreshColle = async (id: number) => { return makePostRequest( `/colles/${id}/refresh`, {}, "Échec de la demande de rafraîchissement de la colle", "POST" ); }; export interface Attachment { path: string; name: string; } /** * === SUBJECTS API === */ export const getSubjects = async () => { return makeRequest("/subjects", "Échec de la récupération des matières"); }; export const useSubjects = () => { const { data, ...props } = useQuery({ queryKey: ["subjects"], queryFn: getSubjects, staleTime: Duration.fromObject({ hours: 1, // 1 day }).toMillis(), gcTime: Duration.fromObject({ days: 3, // 3 days }).toMillis(), }); return { subjects: (data as string[]) || [], ...props, }; }; /** * === NOTIFICATIONS API === */ export const subscribe = async (data: any) => { return makePostRequest( "/notifications/subscribe", data, "Échec de l'abonnement aux notifications" ); } export const unsubscribe = async (data: any) => { return makePostRequest( "/notifications/unsubscribe", data, "Échec de la désinscription des notifications" ); };