224 lines
6.6 KiB
TypeScript
224 lines
6.6 KiB
TypeScript
import { create } from "zustand";
|
|
import { Platform } from "react-native";
|
|
import {
|
|
onAuthStateChanged,
|
|
signOut as firebaseSignOut,
|
|
isWeb,
|
|
getAuthInstance,
|
|
} from "../firebase";
|
|
import { UserProfile, AuthService } from "../services/authServices";
|
|
import { UserWallet } from "../services/walletService";
|
|
import { useUserProfileStore } from "./userProfileStore";
|
|
import { useTransactionStore } from "./transactionStore";
|
|
import { useUserWalletStore } from "./userWalletStore";
|
|
import { withGlobalLoading } from "./uiStore";
|
|
import { awardDailyLoginPoints } from "../services/pointsService";
|
|
|
|
// Conditionally import FCMService only for native
|
|
let FCMService: any = null;
|
|
if (Platform.OS !== "web") {
|
|
FCMService = require("../services/fcmService").FCMService;
|
|
}
|
|
|
|
interface AuthState {
|
|
// User state
|
|
user: any | null;
|
|
profile: UserProfile | null;
|
|
wallet: UserWallet | null;
|
|
|
|
// Loading states
|
|
loading: boolean;
|
|
profileLoading: boolean;
|
|
walletLoading: boolean;
|
|
|
|
// Error states
|
|
profileError: string | null;
|
|
walletError: string | null;
|
|
|
|
// Phone authentication state
|
|
phoneSessionInfo: string | null;
|
|
phoneConfirmationResult: any | null;
|
|
phoneLoading: boolean;
|
|
phoneError: string | null;
|
|
|
|
// Computed values
|
|
formattedBalance: string;
|
|
|
|
// Actions
|
|
setUser: (user: any | null) => void;
|
|
setProfile: (profile: UserProfile | null) => void;
|
|
setWallet: (wallet: UserWallet | null) => void;
|
|
setLoading: (loading: boolean) => void;
|
|
setProfileLoading: (loading: boolean) => void;
|
|
setWalletLoading: (loading: boolean) => void;
|
|
setProfileError: (error: string | null) => void;
|
|
setWalletError: (error: string | null) => void;
|
|
setFormattedBalance: (balance: string) => void;
|
|
|
|
// Phone auth setters
|
|
setPhoneSessionInfo: (sessionInfo: string | null) => void;
|
|
setPhoneConfirmationResult: (confirmationResult: any | null) => void;
|
|
setPhoneLoading: (loading: boolean) => void;
|
|
setPhoneError: (error: string | null) => void;
|
|
clearPhoneAuth: () => void;
|
|
|
|
// Auth actions
|
|
signOut: () => Promise<void>;
|
|
refreshProfile: () => Promise<void>;
|
|
refreshWallet: () => Promise<void>;
|
|
|
|
// Initialize auth listener
|
|
initializeAuth: () => () => void;
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set, get) => ({
|
|
// Initial state
|
|
user: null,
|
|
profile: null,
|
|
wallet: null,
|
|
loading: true,
|
|
profileLoading: false,
|
|
walletLoading: false,
|
|
profileError: null,
|
|
walletError: null,
|
|
phoneSessionInfo: null,
|
|
phoneConfirmationResult: null,
|
|
phoneLoading: false,
|
|
phoneError: null,
|
|
formattedBalance: "0.00",
|
|
|
|
// Setters
|
|
setUser: (user) => set({ user }),
|
|
setProfile: (profile) => set({ profile }),
|
|
setWallet: (wallet) => set({ wallet }),
|
|
setLoading: (loading) => set({ loading }),
|
|
setProfileLoading: (profileLoading) => set({ profileLoading }),
|
|
setWalletLoading: (walletLoading) => set({ walletLoading }),
|
|
setProfileError: (error) => set({ profileError: error }),
|
|
setWalletError: (error) => set({ walletError: error }),
|
|
setFormattedBalance: (formattedBalance) => set({ formattedBalance }),
|
|
|
|
// Phone auth setters
|
|
setPhoneSessionInfo: (phoneSessionInfo) => set({ phoneSessionInfo }),
|
|
setPhoneConfirmationResult: (phoneConfirmationResult) =>
|
|
set({ phoneConfirmationResult }),
|
|
setPhoneLoading: (phoneLoading) => set({ phoneLoading }),
|
|
setPhoneError: (phoneError) => set({ phoneError }),
|
|
|
|
// Phone auth actions
|
|
clearPhoneAuth: () =>
|
|
set({
|
|
phoneSessionInfo: null,
|
|
phoneConfirmationResult: null,
|
|
phoneLoading: false,
|
|
phoneError: null,
|
|
}),
|
|
|
|
// Auth actions
|
|
signOut: async () => {
|
|
// Get current user from our abstracted firebase module
|
|
const currentUser = getAuthInstance().currentUser;
|
|
|
|
// If there is no current Firebase user (common in dev with mocked flows),
|
|
// just clear local state and exit without throwing.
|
|
if (!currentUser) {
|
|
console.warn(
|
|
"AuthStore.signOut called with no current Firebase user; clearing local state only"
|
|
);
|
|
get().setUser(null);
|
|
get().clearPhoneAuth();
|
|
useUserProfileStore.getState().clearAll();
|
|
useTransactionStore.getState().clearAll();
|
|
useUserWalletStore.getState().clearAll();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const uid = get().user?.uid;
|
|
|
|
// Remove FCM token from Firestore before signing out (native only)
|
|
if (uid && FCMService && Platform.OS !== "web") {
|
|
try {
|
|
await FCMService.removeTokenFromFirestore(uid);
|
|
FCMService.cleanup();
|
|
} catch (fcmError) {
|
|
console.error("Error removing FCM token on sign out:", fcmError);
|
|
}
|
|
}
|
|
|
|
await withGlobalLoading(async () => {
|
|
// Sign out from Google if signed in with Google
|
|
await AuthService.signOutFromGoogle();
|
|
// Sign out from Firebase
|
|
await firebaseSignOut();
|
|
});
|
|
|
|
get().clearPhoneAuth();
|
|
useUserProfileStore.getState().clearAll();
|
|
useTransactionStore.getState().clearAll();
|
|
useUserWalletStore.getState().clearAll();
|
|
} catch (error) {
|
|
console.error("Error signing out:", error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
refreshProfile: async () => {
|
|
const uid = get().user?.uid;
|
|
if (!uid) {
|
|
console.warn(
|
|
"AuthStore.refreshProfile called without an authenticated user"
|
|
);
|
|
return;
|
|
}
|
|
|
|
const profileStore = useUserProfileStore.getState();
|
|
profileStore.ensureSubscription(uid);
|
|
await withGlobalLoading(() => profileStore.refreshProfile(uid));
|
|
},
|
|
|
|
refreshWallet: async () => {
|
|
const uid = get().user?.uid;
|
|
if (!uid) {
|
|
console.warn(
|
|
"AuthStore.refreshWallet called without an authenticated user"
|
|
);
|
|
return;
|
|
}
|
|
|
|
const walletStore = useUserWalletStore.getState();
|
|
walletStore.ensureSubscription(uid);
|
|
await withGlobalLoading(() => walletStore.refreshWallet(uid));
|
|
},
|
|
|
|
// Initialize auth listener
|
|
initializeAuth: () => {
|
|
console.log("AuthStore: Initializing auth listener");
|
|
const unsubscribe = onAuthStateChanged((fbUser: any) => {
|
|
// In dev, if we've explicitly set a fake emulator user, don't let
|
|
// Firebase auth (which will report null) wipe it out.
|
|
if (__DEV__) {
|
|
const current = get().user;
|
|
if (current?.uid === "dev-emulator-user") {
|
|
if (get().loading) {
|
|
set({ loading: false });
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
console.log("AuthStore: Auth state changed, user:", fbUser?.uid);
|
|
set({ user: fbUser || null, loading: false });
|
|
if (fbUser) {
|
|
awardDailyLoginPoints().catch((error) => {
|
|
console.warn(
|
|
"[AuthStore] Failed to maybe award daily login points",
|
|
error
|
|
);
|
|
});
|
|
}
|
|
});
|
|
return unsubscribe;
|
|
},
|
|
}));
|