xtablo-source/ui/src/hooks/auth.ts
Arthur Belleville 374a1ec4b8
Fix lint
2025-10-10 11:10:28 +02:00

273 lines
7.3 KiB
TypeScript

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<Record<string, string>>({});
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<string, string> = {};
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<Record<string, string>>({});
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<string, string> = {};
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<Record<string, string>>({});
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();
},
});
}