Add guest auth and save-account flow

Introduce anonymous sign-in with in-app saving, guest recovery warnings, paywall prompts, and admin guest labeling.
This commit is contained in:
jjbrunton committed 2026-01-17 17:55:06 +00:00
1 parent ce68d57a49
commit 349f460e15
14 files changed
+1236 -176

No files matched your search

+40 -32
View File
@@ -4,12 +4,14 @@ import { Events } from "../lib/analytics";
import type { Profile, Couple } from "@/types";
interface AuthState {
user: Profile | null;
couple: Couple | null;
partner: Profile | null;
isLoading: boolean;
isAuthenticated: boolean;
interface AuthState {
user: Profile | null;
couple: Couple | null;
partner: Profile | null;
isLoading: boolean;
isAuthenticated: boolean;
isAnonymous: boolean;
// Actions
fetchUser: () => Promise<void>;
@@ -36,18 +38,19 @@ export const useAuthStore = create<AuthState>((set, get) => ({
user: null,
couple: null,
partner: null,
isLoading: true,
isAuthenticated: false,
isLoading: true,
isAuthenticated: false,
isAnonymous: false,
fetchUser: async () => {
try {
// First check if we have a session in storage
const { data: { session } } = await supabase.auth.getSession();
if (!session?.user) {
set({ user: null, isAuthenticated: false, isLoading: false });
return;
}
if (!session?.user) {
set({ user: null, isAuthenticated: false, isAnonymous: false, isLoading: false });
return;
}
// Validate the session by fetching the user from the server
// This will fail if the session was deleted server-side
@@ -56,8 +59,8 @@ export const useAuthStore = create<AuthState>((set, get) => ({
if (authError || !authUser) {
console.log("[Auth] Session invalid, signing out:", authError?.message);
// Session is invalid - clear everything
set({ user: null, couple: null, partner: null, isAuthenticated: false, isLoading: false });
// Session is invalid - clear everything
set({ user: null, couple: null, partner: null, isAuthenticated: false, isAnonymous: false, isLoading: false });
// Clear other stores
const { useMatchStore, usePacksStore, useMessageStore, useSubscriptionStore, useNotificationPreferencesStore, useStreakStore } = getOtherStores();
useMatchStore.getState().clearMatches();
@@ -77,10 +80,13 @@ export const useAuthStore = create<AuthState>((set, get) => ({
.eq("id", authUser.id)
.maybeSingle();
set({
user: profile,
isAuthenticated: true,
});
const isAnonymous = !!(authUser as any).is_anonymous;
set({
user: profile,
isAuthenticated: true,
isAnonymous,
});
// If user has a couple, fetch couple data; otherwise clear couple/partner
if (profile?.couple_id) {
@@ -142,13 +148,14 @@ export const useAuthStore = create<AuthState>((set, get) => ({
Events.signOut();
// Clear local state FIRST to ensure UI updates even if Supabase call fails
set({
user: null,
couple: null,
partner: null,
isAuthenticated: false,
isLoading: false,
});
set({
user: null,
couple: null,
partner: null,
isAuthenticated: false,
isAnonymous: false,
isLoading: false,
});
// Clear other stores
const { useMatchStore, usePacksStore, useMessageStore, useSubscriptionStore, useNotificationPreferencesStore, useStreakStore } = getOtherStores();
useMatchStore.getState().clearMatches();
@@ -171,13 +178,14 @@ export const useAuthStore = create<AuthState>((set, get) => ({
},
setUser: (user) => {
set({
user,
isAuthenticated: !!user,
isLoading: false,
// Clear couple/partner when user is null (signed out)
...(user === null && { couple: null, partner: null })
});
set({
user,
isAuthenticated: !!user,
...(user === null && { isAnonymous: false }),
isLoading: false,
// Clear couple/partner when user is null (signed out)
...(user === null && { couple: null, partner: null })
});
// Clear other stores when user signs out
if (user === null) {
const { useMatchStore, usePacksStore, useMessageStore, useSubscriptionStore, useNotificationPreferencesStore, useStreakStore } = getOtherStores();