import { create } from "zustand"; import { Provider, Session } from "@supabase/supabase-js"; import { supabase } from "@/lib/supabase"; import * as WebBrowser from "expo-web-browser"; import { makeRedirectUri } from "expo-auth-session"; import * as QueryParams from "expo-auth-session/build/QueryParams"; import { Linking } from "react-native"; import { QueryClient } from "@tanstack/react-query"; import { User } from "@/types/user.types"; import { api } from "@/lib/api"; import * as AppleAuthentication from "expo-apple-authentication"; import { ensurePurchasesConfigured } from "@/lib/purchases"; import { organizationBillingQueryKey } from "@/hooks/organization"; interface AuthState { session: Session | null; user: User | null; loading: boolean; initialized: boolean; initialize: (queryClient: QueryClient) => Promise; setSession: (session: Session | null) => void; login: (email: string, password: string) => Promise; signUp: ( email: string, password: string, firstName: string, lastName: string, companyName: string ) => Promise; performOAuth: (provider: Provider) => Promise; signInWithApple: () => Promise; signOut: () => Promise; createSessionFromUrl: (url: string) => Promise; fetchAndSetUser: (session: Session | null, queryClient?: QueryClient) => Promise; } WebBrowser.maybeCompleteAuthSession(); const redirectTo = makeRedirectUri({ path: "/(app)/(tabs)" }); export const useAuthStore = create((set, get) => ({ user: null, session: null, loading: true, initialized: false, setSession: (session: Session | null) => set({ session }), fetchAndSetUser: async (session: Session | null, queryClient?: QueryClient) => { if (!session) { set({ user: null }); queryClient?.removeQueries({ queryKey: organizationBillingQueryKey }); return; } try { const { data } = await api.get("/api/v1/users/me", { headers: { Authorization: `Bearer ${session?.access_token}`, }, }); set({ user: data }); await ensurePurchasesConfigured(data.id); await queryClient?.invalidateQueries({ queryKey: organizationBillingQueryKey }); } catch (error) { console.error("Error fetching user:", error); } }, initialize: async (queryClient: QueryClient) => { try { const { data: { session }, } = await supabase.auth.getSession(); set({ session, }); await get().fetchAndSetUser(session, queryClient); supabase.auth.onAuthStateChange(async (event, session) => { set({ session, }); await get().fetchAndSetUser(session, queryClient); }); const initialUrl = await Linking.getInitialURL(); if (initialUrl) { await get().createSessionFromUrl(initialUrl); } Linking.addEventListener("url", ({ url }) => { get().createSessionFromUrl(url); }); } catch (error) { console.error("Auth initialization error:", error); } finally { set({ loading: false }); set({ initialized: true }); queryClient.invalidateQueries({ queryKey: ["user"] }); } }, login: async (email: string, password: string) => { const { error } = await supabase.auth.signInWithPassword({ email, password, }); if (error) throw error; set({ loading: false }); }, signUp: async ( email: string, password: string, firstName: string, lastName: string, companyName: string ) => { set({ loading: true }); try { const { data, error } = await supabase.auth.signUp({ email, password, options: { data: { company_name: companyName, first_name: firstName, last_name: lastName, name: [firstName, lastName].filter(Boolean).join(" ").trim(), }, }, }); if (error) { throw error; } if (!data.session) { throw new Error( "Impossible d'ouvrir votre session après l'inscription. Si cette adresse existe déjà, essayez de vous connecter." ); } } finally { set({ loading: false }); } }, performOAuth: async (provider: Provider) => { const { data, error } = await supabase.auth.signInWithOAuth({ provider, options: { redirectTo, skipBrowserRedirect: true, queryParams: { access_type: "offline", prompt: "consent", }, }, }); if (error) throw error; const res = await WebBrowser.openAuthSessionAsync(data?.url ?? "", redirectTo); if (res.type === "success") { const { url } = res; await get().createSessionFromUrl(url); } }, signInWithApple: async () => { try { const credential = await AppleAuthentication.signInAsync({ requestedScopes: [ AppleAuthentication.AppleAuthenticationScope.FULL_NAME, AppleAuthentication.AppleAuthenticationScope.EMAIL, ], }); if (credential.identityToken) { const { error, data: { session }, } = await supabase.auth.signInWithIdToken({ provider: "apple", token: credential.identityToken, }); if (!error && session) { await set({ session }); } } else { throw new Error("No identityToken."); } } catch (e) { console.error("Error signing in with Apple:", e); } }, signOut: async () => { await supabase.auth.signOut(); set({ loading: false, user: null, session: null }); }, createSessionFromUrl: async (url: string) => { const { params, errorCode } = QueryParams.getQueryParams(url); if (errorCode) throw new Error(errorCode); const { access_token, refresh_token } = params; if (!access_token) return; const { data, error } = await supabase.auth.setSession({ access_token, refresh_token, }); if (error) throw error; set({ session: data.session }); }, }));