211 lines
6 KiB
TypeScript
211 lines
6 KiB
TypeScript
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<void>;
|
|
setSession: (session: Session | null) => void;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
signUp: (
|
|
email: string,
|
|
password: string,
|
|
firstName: string,
|
|
lastName: string,
|
|
companyName: string
|
|
) => Promise<void>;
|
|
performOAuth: (provider: Provider) => Promise<void>;
|
|
signInWithApple: () => Promise<void>;
|
|
signOut: () => Promise<void>;
|
|
createSessionFromUrl: (url: string) => Promise<void>;
|
|
fetchAndSetUser: (session: Session | null, queryClient?: QueryClient) => Promise<void>;
|
|
}
|
|
|
|
WebBrowser.maybeCompleteAuthSession();
|
|
const redirectTo = makeRedirectUri({ path: "/(app)/(tabs)" });
|
|
|
|
export const useAuthStore = create<AuthState>((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<User>("/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 });
|
|
},
|
|
}));
|