import { createClient, Session, User as SupabaseUser } from "@supabase/supabase-js"; import { useMutation } from "@tanstack/react-query"; import { api, queryClient } from "@ui/lib/api"; import { toast } from "@ui/ui-library/toast/toast-queue"; import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { match } from "ts-pattern"; export type User = SupabaseUser & { user_metadata: { email: string; email_verified: boolean; first_name: string; last_name: string; business_name: string; }; }; const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY; if (!supabaseUrl || !supabaseAnonKey) { throw new Error("Missing Supabase environment variables"); } export const supabase = createClient(supabaseUrl, supabaseAnonKey); interface SignUpData { email: string; password: string; confirm_password: string; first_name: string; last_name: string; business_name: string; } interface LoginData { email: string; password: string; } interface AuthResponse { user: SupabaseUser | null; session: Session | null; } export function useSignUp({ redirectUrl }: { redirectUrl: string | null }) { const navigate = useNavigate(); const [errors, setErrors] = useState>({}); const { signUpToStream } = useSignUpToStream(); const { mutate, isPending } = useMutation< AuthResponse, { message: string; code: string }, SignUpData >({ mutationFn: async (data: SignUpData) => { const { data: response, error } = await supabase.auth.signUp({ email: data.email, password: data.password, options: { data: { first_name: data.first_name, last_name: data.last_name, business_name: data.business_name, }, }, }); if (error) throw error; if (response.session?.access_token) { await signUpToStream(response.session.access_token); } return response; }, onSuccess: () => { if (redirectUrl) { localStorage.removeItem("redirectUrl"); navigate(decodeURIComponent(redirectUrl)); } else { navigate("/"); } }, onError: (error) => { const errMap: Record = {}; match(error.code) .with("user_already_exists", () => { errMap["email"] = "Cette adresse email est déjà utilisée"; }) .otherwise(() => { toast.add( { title: "Erreur", description: error.message, type: "error", position: "top-left", }, { timeout: 5000, } ); }); setErrors(errMap); }, }); return { mutate, isPending, errors }; } export function useSignUpWithoutPassword() { const [errors, setErrors] = useState>({}); const { signUpToStream } = useSignUpToStream(); const { mutateAsync, isPending } = useMutation< AuthResponse, { message: string; code: string }, { email: string; name: string } >({ mutationFn: async (data: { email: string; name: string }) => { // Generate a temporary password for the user const tempPassword = Math.random().toString(36).slice(-8) + Math.random().toString(36).slice(-8); const { data: response, error } = await supabase.auth.signUp({ email: data.email.trim(), password: tempPassword, options: { data: { first_name: data.name.trim().split(" ")[0] || "", last_name: data.name.trim().split(" ").slice(1).join(" ") || "", business_name: "", }, }, }); if (error) throw error; if (response.session?.access_token) { await signUpToStream(response.session.access_token); } // Mark the user as temporary if (response.session?.access_token) { await api.post( "/api/v1/users/mark-temporary", { temporary_password: tempPassword, }, { headers: { Authorization: `Bearer ${response.session.access_token}`, }, } ); } return response; }, onError: (error) => { const errMap: Record = {}; match(error.code) .with("user_already_exists", () => { errMap["email"] = "Cette adresse email est déjà utilisée"; }) .otherwise(() => { toast.add( { title: "Erreur", description: error.message, type: "error", position: "top-left", }, { timeout: 5000, } ); }); setErrors(errMap); }, }); return { mutateAsync, isPending, errors }; } export function useSignUpToStream() { const { mutate: signUpToStream } = useMutation({ mutationFn: async (accessToken: string) => { const { data } = await api.post<{ streamToken: string }>( "/api/v1/users/sign-up-to-stream", {}, { headers: { Authorization: `Bearer ${accessToken}`, }, } ); return data; }, }); return { signUpToStream }; } export function useLoginEmail({ redirectUrl }: { redirectUrl: string | null }) { const navigate = useNavigate(); const [errors, setErrors] = useState>({}); const { signUpToStream } = useSignUpToStream(); const { mutate, isPending } = useMutation< AuthResponse, { message: string; code: string }, LoginData >({ mutationFn: async (data: LoginData) => { const { data: response, error } = await supabase.auth.signInWithPassword({ email: data.email.trim(), password: data.password.trim(), }); if (error) throw error; if (response.session?.access_token) { await signUpToStream(response.session.access_token); } return response; }, onSuccess: () => { if (redirectUrl) { localStorage.removeItem("redirectUrl"); navigate(decodeURIComponent(redirectUrl)); } else { navigate("/"); } }, onError: (error) => { match(error.code) .with("invalid_credentials", () => { setErrors({ email: "Email ou mot de passe incorrect" }); }) .otherwise(() => { toast.add( { title: "Erreur", description: error.message, type: "error", position: "top-left", }, { timeout: 5000, } ); }); }, }); return { mutate, isPending, errors }; } export function useLoginGoogle() { const { mutate } = useMutation({ mutationFn: async () => { const { data, error } = await supabase.auth.signInWithOAuth({ provider: "google", options: { redirectTo: `${window.location.origin}/login-with-oauth`, }, }); if (error) throw error; return data; }, }); return { loginWithGoogle: mutate }; } export function useLogout() { return useMutation({ mutationFn: async () => { const { error } = await supabase.auth.signOut(); if (error) throw error; queryClient.removeQueries(); }, }); }