ExposedGays: map, shop, auth, Supabase schema, Coolify Dockerfile
This commit is contained in:
189
src/lib/auth/context.tsx
Normal file
189
src/lib/auth/context.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
"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,
|
||||
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;
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user