managarten/manadeck/apps/mobile/store/authStore.ts
Till-JS e7f5f942f3 chore: initial commit - consolidate 4 projects into monorepo
Projects included:
- maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing)
- manacore (Expo mobile + SvelteKit web + Astro landing)
- manadeck (NestJS backend + Expo mobile + SvelteKit web)
- memoro (Expo mobile + SvelteKit web + Astro landing)

This commit preserves the current state before monorepo restructuring.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 23:38:24 +01:00

246 lines
6.5 KiB
TypeScript

import { create } from 'zustand';
import { authService } from '../services/authService';
import { tokenManager, TokenState } from '../services/tokenManager';
import { supabase } from '../utils/supabase';
import type { ManaUser } from '../types/auth';
interface AuthState {
user: ManaUser | null;
isLoading: boolean;
isInitialized: boolean;
error: string | null;
initialize: () => Promise<void>;
signIn: (email: string, password: string) => Promise<void>;
signUp: (email: string, password: string, username: string) => Promise<void>;
signInWithGoogle: (idToken: string) => Promise<void>;
signInWithApple: (identityToken: string) => Promise<void>;
signOut: () => Promise<void>;
resetPassword: (email: string) => Promise<void>;
updateProfile: (updates: {
username?: string;
display_name?: string;
bio?: string;
avatar_url?: string;
}) => Promise<void>;
clearError: () => void;
}
export const useAuthStore = create<AuthState>((set, get) => ({
user: null,
isLoading: false,
isInitialized: false,
error: null,
initialize: async () => {
try {
set({ isLoading: true });
// Register callback for token refresh to update user data
authService.onTokenRefresh = (userData) => {
console.debug('Token refreshed, updating user data in store');
const currentUser = get().user;
if (currentUser) {
set({
user: {
...currentUser,
id: userData.id,
email: userData.email,
role: userData.role,
},
});
}
};
// Subscribe to TokenManager state changes
tokenManager.subscribe((state, token) => {
console.debug(`TokenManager state changed: ${state}`);
if (state === TokenState.EXPIRED) {
// Token expired, clear user
const currentUser = get().user;
if (currentUser) {
console.debug('Token expired, clearing user from store');
set({ user: null });
}
}
});
// Check if user is authenticated via Mana Core
const appToken = await authService.getAppToken();
if (appToken && authService.isTokenValidLocally(appToken)) {
// Token exists and is valid locally
const user = authService.getUserFromToken(appToken);
if (user) {
set({
user,
isInitialized: true,
});
} else {
set({ isInitialized: true });
}
} else if (appToken) {
// Token exists but might be expired - let TokenManager handle refresh
console.debug('Token exists but may be expired, will attempt refresh on first API call');
set({ isInitialized: true });
} else {
// No token at all
await authService.clearAuthStorage();
set({ isInitialized: true });
}
} catch (error) {
console.error('Auth initialization error:', error);
// Don't throw - continue with unauthenticated state
set({ error: null, isInitialized: true, user: null });
} finally {
set({ isLoading: false });
}
},
signIn: async (email: string, password: string) => {
try {
set({ isLoading: true, error: null });
const result = await authService.signIn(email, password);
if (!result.success) {
throw new Error(result.error || 'Failed to sign in');
}
set({
user: result.user || null,
});
} catch (error: any) {
set({ error: error.message || 'Failed to sign in' });
throw error;
} finally {
set({ isLoading: false });
}
},
signUp: async (email: string, password: string, username: string) => {
try {
set({ isLoading: true, error: null });
const result = await authService.signUp(email, password, username);
if (!result.success) {
throw new Error(result.error || 'Failed to sign up');
}
set({
user: result.user || null,
});
} catch (error: any) {
set({ error: error.message || 'Failed to sign up' });
throw error;
} finally {
set({ isLoading: false });
}
},
signInWithGoogle: async (idToken: string) => {
try {
set({ isLoading: true, error: null });
const result = await authService.signInWithGoogle(idToken);
if (!result.success) {
throw new Error(result.error || 'Google sign-in failed');
}
set({
user: result.user || null,
});
} catch (error: any) {
set({ error: error.message || 'Google sign-in failed' });
throw error;
} finally {
set({ isLoading: false });
}
},
signInWithApple: async (identityToken: string) => {
try {
set({ isLoading: true, error: null });
const result = await authService.signInWithApple(identityToken);
if (!result.success) {
throw new Error(result.error || 'Apple sign-in failed');
}
set({
user: result.user || null,
});
} catch (error: any) {
set({ error: error.message || 'Apple sign-in failed' });
throw error;
} finally {
set({ isLoading: false });
}
},
signOut: async () => {
try {
set({ isLoading: true, error: null });
await authService.signOut();
// Clear TokenManager state
await tokenManager.clearTokens();
set({
user: null,
});
} catch (error: any) {
set({ error: error.message || 'Failed to sign out' });
throw error;
} finally {
set({ isLoading: false });
}
},
resetPassword: async (email: string) => {
try {
set({ isLoading: true, error: null });
const result = await authService.resetPassword(email);
if (!result.success) {
throw new Error(result.error || 'Failed to send reset email');
}
} catch (error: any) {
set({ error: error.message || 'Failed to send reset email' });
throw error;
} finally {
set({ isLoading: false });
}
},
updateProfile: async (updates) => {
try {
set({ isLoading: true, error: null });
const user = get().user;
if (!user) throw new Error('No user logged in');
const { error } = await supabase
.from('profiles')
.update({
...updates,
updated_at: new Date().toISOString(),
})
.eq('id', user.id);
if (error) throw error;
} catch (error: any) {
set({ error: error.message || 'Failed to update profile' });
throw error;
} finally {
set({ isLoading: false });
}
},
clearError: () => set({ error: null }),
}));