195 lines
6.1 KiB
TypeScript
195 lines
6.1 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useState,
|
|
useCallback,
|
|
type ReactNode,
|
|
} from "react";
|
|
import { createClient } from "@/lib/supabase/client";
|
|
import type { User, Session } from "@supabase/supabase-js";
|
|
import type { Profile } from "@/types";
|
|
|
|
interface AuthContextValue {
|
|
user: User | null;
|
|
session: Session | null;
|
|
profile: Profile | null;
|
|
loading: boolean;
|
|
signUp: (email: string, password: string) => Promise<{ error: string | null }>;
|
|
signIn: (email: string, password: string) => Promise<{ error: string | null }>;
|
|
signInAnonymously: () => Promise<{ error: string | null }>;
|
|
signOut: () => Promise<void>;
|
|
updateProfile: (data: Partial<Profile>) => Promise<{ error: string | null }>;
|
|
refreshProfile: () => Promise<void>;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
|
|
function mapDbProfile(row: Record<string, unknown>): Profile {
|
|
const loc = row.location as { coordinates?: [number, number] } | null;
|
|
return {
|
|
id: row.id as string,
|
|
username: (row.username as string) ?? null,
|
|
display_name: (row.display_name as string) ?? null,
|
|
age: (row.age as number) ?? null,
|
|
position: (row.position as Profile["position"]) ?? null,
|
|
bio: (row.bio as string) ?? null,
|
|
avatar_url: (row.avatar_url as string) ?? null,
|
|
status: (row.status as Profile["status"]) ?? "offline",
|
|
is_anonymous: (row.is_anonymous as boolean) ?? true,
|
|
kinks: (row.kinks as string[]) ?? [],
|
|
sti_status: (row.sti_status as string) ?? null,
|
|
on_prep: (row.on_prep as boolean) ?? false,
|
|
last_active: (row.last_active as string) ?? new Date().toISOString(),
|
|
lat: loc?.coordinates?.[1] ?? null,
|
|
lng: loc?.coordinates?.[0] ?? null,
|
|
city: (row.city as string) ?? null,
|
|
state: (row.state as string) ?? null,
|
|
id_verified: (row.id_verified as boolean) ?? false,
|
|
id_verified_at: (row.id_verified_at as string) ?? null,
|
|
vanilla_mode: (row.vanilla_mode as boolean) ?? false,
|
|
created_at: (row.created_at as string) ?? new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const supabase = createClient();
|
|
const [user, setUser] = useState<User | null>(null);
|
|
const [session, setSession] = useState<Session | null>(null);
|
|
const [profile, setProfile] = useState<Profile | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const fetchProfile = useCallback(
|
|
async (userId: string) => {
|
|
const { data, error } = await supabase
|
|
.from("profiles")
|
|
.select("*")
|
|
.eq("id", userId)
|
|
.single();
|
|
|
|
if (!error && data) {
|
|
setProfile(mapDbProfile(data));
|
|
}
|
|
},
|
|
[supabase]
|
|
);
|
|
|
|
useEffect(() => {
|
|
supabase.auth.getSession().then(({ data: { session: s } }) => {
|
|
setSession(s);
|
|
setUser(s?.user ?? null);
|
|
if (s?.user) fetchProfile(s.user.id);
|
|
setLoading(false);
|
|
});
|
|
|
|
const {
|
|
data: { subscription },
|
|
} = supabase.auth.onAuthStateChange((_event, s) => {
|
|
setSession(s);
|
|
setUser(s?.user ?? null);
|
|
if (s?.user) {
|
|
fetchProfile(s.user.id);
|
|
} else {
|
|
setProfile(null);
|
|
}
|
|
});
|
|
|
|
return () => subscription.unsubscribe();
|
|
}, [supabase, fetchProfile]);
|
|
|
|
const signUp = async (email: string, password: string) => {
|
|
const { error } = await supabase.auth.signUp({
|
|
email,
|
|
password,
|
|
options: { emailRedirectTo: `${window.location.origin}/profile` },
|
|
});
|
|
return { error: error?.message ?? null };
|
|
};
|
|
|
|
const signIn = async (email: string, password: string) => {
|
|
const { error } = await supabase.auth.signInWithPassword({ email, password });
|
|
return { error: error?.message ?? null };
|
|
};
|
|
|
|
const signInAnonymously = async () => {
|
|
const { error } = await supabase.auth.signInAnonymously();
|
|
return { error: error?.message ?? null };
|
|
};
|
|
|
|
const signOut = async () => {
|
|
await supabase.auth.signOut();
|
|
setProfile(null);
|
|
};
|
|
|
|
const updateProfile = async (data: Partial<Profile>) => {
|
|
if (!user) return { error: "Not signed in" };
|
|
|
|
const payload: Record<string, unknown> = {
|
|
updated_at: new Date().toISOString(),
|
|
last_active: new Date().toISOString(),
|
|
};
|
|
|
|
if (data.display_name !== undefined) payload.display_name = data.display_name;
|
|
if (data.username !== undefined) payload.username = data.username;
|
|
if (data.age !== undefined) payload.age = data.age;
|
|
if (data.position !== undefined) payload.position = data.position;
|
|
if (data.bio !== undefined) payload.bio = data.bio;
|
|
if (data.kinks !== undefined) payload.kinks = data.kinks;
|
|
if (data.sti_status !== undefined) payload.sti_status = data.sti_status;
|
|
if (data.on_prep !== undefined) payload.on_prep = data.on_prep;
|
|
if (data.is_anonymous !== undefined) payload.is_anonymous = data.is_anonymous;
|
|
if (data.status !== undefined) payload.status = data.status;
|
|
if (data.city !== undefined) payload.city = data.city;
|
|
if (data.state !== undefined) payload.state = data.state;
|
|
if (data.id_verified !== undefined) payload.id_verified = data.id_verified;
|
|
if (data.id_verified_at !== undefined) payload.id_verified_at = data.id_verified_at;
|
|
const { error } = await supabase
|
|
.from("profiles")
|
|
.update(payload)
|
|
.eq("id", user.id);
|
|
|
|
if (error) return { error: error.message };
|
|
|
|
if (data.lat != null && data.lng != null) {
|
|
await supabase.rpc("update_profile_location", {
|
|
p_user_id: user.id,
|
|
p_lat: data.lat,
|
|
p_lng: data.lng,
|
|
});
|
|
}
|
|
|
|
await fetchProfile(user.id);
|
|
return { error: null };
|
|
};
|
|
|
|
const refreshProfile = async () => {
|
|
if (user) await fetchProfile(user.id);
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
user,
|
|
session,
|
|
profile,
|
|
loading,
|
|
signUp,
|
|
signIn,
|
|
signInAnonymously,
|
|
signOut,
|
|
updateProfile,
|
|
refreshProfile,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
|
|
return ctx;
|
|
} |