186 lines
4.8 KiB
TypeScript
186 lines
4.8 KiB
TypeScript
import React, {
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
useCallback,
|
|
type ReactNode,
|
|
} from "react";
|
|
import type { User as SupabaseUser } from "@supabase/supabase-js";
|
|
import { supabase } from "@/lib/supabase";
|
|
|
|
const ALLOWED_DOMAIN = "gomezleemarketing.com";
|
|
const GERARDO_EMAIL = "gmarrero@gomezleemarketing.com";
|
|
const DELETE_ALLOWED_EMAILS = [GERARDO_EMAIL, "areyes@gomezleemarketing.com"];
|
|
|
|
function isAllowedEmail(email: string | null | undefined): boolean {
|
|
if (!email) return false;
|
|
return email.toLowerCase().endsWith(`@${ALLOWED_DOMAIN}`);
|
|
}
|
|
|
|
function getRedirectTo(): string {
|
|
if (typeof window === "undefined") return "/tablero-cdc/";
|
|
return new URL(import.meta.env.BASE_URL || "/", window.location.origin).toString();
|
|
}
|
|
|
|
export interface AuthUser {
|
|
id: string;
|
|
uid: string;
|
|
email: string | null;
|
|
displayName: string | null;
|
|
photoURL: string | null;
|
|
raw: SupabaseUser;
|
|
}
|
|
|
|
function normalizeSupabaseUser(user: SupabaseUser): AuthUser {
|
|
const metadata = user.user_metadata || {};
|
|
|
|
const displayName =
|
|
metadata.full_name ||
|
|
metadata.name ||
|
|
metadata.display_name ||
|
|
user.email?.split("@")[0] ||
|
|
null;
|
|
|
|
const photoURL = metadata.avatar_url || metadata.picture || null;
|
|
|
|
return {
|
|
id: user.id,
|
|
uid: user.id,
|
|
email: user.email ?? null,
|
|
displayName,
|
|
photoURL,
|
|
raw: user,
|
|
};
|
|
}
|
|
|
|
interface AuthContextValue {
|
|
user: AuthUser | null;
|
|
loading: boolean;
|
|
error: string | null;
|
|
isGerardo: boolean;
|
|
canDeleteProjects: boolean;
|
|
loginWithGoogle: () => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<AuthUser | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const applyUser = useCallback(async (supabaseUser: SupabaseUser | null) => {
|
|
if (!supabaseUser) {
|
|
setUser(null);
|
|
return;
|
|
}
|
|
|
|
if (!isAllowedEmail(supabaseUser.email)) {
|
|
await supabase.auth.signOut();
|
|
setUser(null);
|
|
setError("Solo se permite acceso con correos corporativos de GomezLee Marketing.");
|
|
return;
|
|
}
|
|
|
|
setUser(normalizeSupabaseUser(supabaseUser));
|
|
setError(null);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
|
|
supabase.auth
|
|
.getSession()
|
|
.then(async ({ data, error: sessionError }) => {
|
|
if (!mounted) return;
|
|
|
|
if (sessionError) {
|
|
console.error("Error al recuperar sesión de Supabase:", sessionError);
|
|
setUser(null);
|
|
setError("No se pudo recuperar la sesión. Intenta iniciar sesión de nuevo.");
|
|
return;
|
|
}
|
|
|
|
await applyUser(data.session?.user ?? null);
|
|
})
|
|
.catch((err: unknown) => {
|
|
if (!mounted) return;
|
|
console.error("Error inesperado al recuperar sesión:", err);
|
|
setUser(null);
|
|
setError("No se pudo recuperar la sesión. Intenta iniciar sesión de nuevo.");
|
|
})
|
|
.finally(() => {
|
|
if (mounted) setLoading(false);
|
|
});
|
|
|
|
const { data: listener } = supabase.auth.onAuthStateChange((_event, session) => {
|
|
void applyUser(session?.user ?? null);
|
|
setLoading(false);
|
|
});
|
|
|
|
return () => {
|
|
mounted = false;
|
|
listener.subscription.unsubscribe();
|
|
};
|
|
}, [applyUser]);
|
|
|
|
const loginWithGoogle = useCallback(async () => {
|
|
setError(null);
|
|
|
|
const { error: loginError } = await supabase.auth.signInWithOAuth({
|
|
provider: "google",
|
|
options: {
|
|
redirectTo: getRedirectTo(),
|
|
queryParams: {
|
|
prompt: "select_account",
|
|
hd: ALLOWED_DOMAIN,
|
|
},
|
|
},
|
|
});
|
|
|
|
if (loginError) {
|
|
console.error("Error al iniciar sesión con Google:", loginError);
|
|
setError("No se pudo iniciar sesión. Intenta de nuevo.");
|
|
}
|
|
}, []);
|
|
|
|
const logout = useCallback(async () => {
|
|
const { error: logoutError } = await supabase.auth.signOut();
|
|
|
|
if (logoutError) {
|
|
console.error("Error al cerrar sesión:", logoutError);
|
|
setError("No se pudo cerrar sesión. Intenta de nuevo.");
|
|
return;
|
|
}
|
|
|
|
setUser(null);
|
|
setError(null);
|
|
}, []);
|
|
|
|
const normalizedEmail = user?.email?.toLowerCase() || "";
|
|
const isGerardo = useMemo(() => normalizedEmail === GERARDO_EMAIL, [normalizedEmail]);
|
|
const canDeleteProjects = useMemo(
|
|
() => DELETE_ALLOWED_EMAILS.includes(normalizedEmail),
|
|
[normalizedEmail],
|
|
);
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{ user, loading, error, isGerardo, canDeleteProjects, loginWithGoogle, logout }}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth(): AuthContextValue {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) {
|
|
throw new Error("useAuth debe usarse dentro de <AuthProvider>");
|
|
}
|
|
return ctx;
|
|
}
|