mirror of
https://github.com/R0m1k3/Reviflow.git
synced 2026-10-21 14:08:07 +02:00
Sauvegarde de l'état (réparation)
This commit is contained in:
commit
7c04fffe77
434 files changed
+83923
No files matched your search
@@ -0,0 +1,19 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
interface AccessibilityState {
|
||||
isDyslexic: boolean;
|
||||
toggleDyslexic: () => void;
|
||||
}
|
||||
|
||||
export const useAccessibility = create<AccessibilityState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
isDyslexic: false,
|
||||
toggleDyslexic: () => set((state) => ({ isDyslexic: !state.isDyslexic })),
|
||||
}),
|
||||
{
|
||||
name: 'accessibility-storage',
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,224 @@
|
||||
import { create } from 'zustand';
|
||||
import { api } from '../lib/api';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
username?: string;
|
||||
is_active: boolean;
|
||||
is_superuser: boolean;
|
||||
role: 'parent' | 'learner';
|
||||
first_name?: string;
|
||||
openrouter_api_key?: string;
|
||||
total_tokens_used?: number;
|
||||
total_cost_usd?: number;
|
||||
parent_id?: string;
|
||||
has_parental_pin?: boolean;
|
||||
learner_profile?: LearnerProfile;
|
||||
}
|
||||
|
||||
interface LearnerProfile {
|
||||
id: string;
|
||||
first_name: string;
|
||||
avatar_url: string | null;
|
||||
streak_current?: number;
|
||||
streak_max?: number;
|
||||
xp?: number;
|
||||
level?: number;
|
||||
badges?: Badge[];
|
||||
}
|
||||
|
||||
export interface Badge {
|
||||
id: string;
|
||||
badge_code: string;
|
||||
earned_at: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
isParentalModeActive: boolean;
|
||||
learnerProfiles: LearnerProfile[];
|
||||
activeLearner: LearnerProfile | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
|
||||
// Actions
|
||||
login: (credentials: any) => Promise<void>;
|
||||
register: (credentials: any) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
checkAuth: (refresh?: boolean) => Promise<void>;
|
||||
verifyParentalGate: (pinOrPassword: { pin?: string; password?: string }) => Promise<void>;
|
||||
fetchProfiles: () => Promise<void>;
|
||||
selectProfile: (learnerId: string) => Promise<void>;
|
||||
updateProfile: (data: { first_name?: string; avatar_url?: string }) => Promise<void>;
|
||||
createChildAccount: (data: { username: string; password?: string; first_name: string; avatar_url: string }) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useAuth = create<AuthState>((set, get) => ({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isParentalModeActive: false,
|
||||
learnerProfiles: [],
|
||||
activeLearner: null,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
|
||||
login: async (credentials) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
// 1. Login (Get Token/Cookie)
|
||||
const formData = new URLSearchParams();
|
||||
// In our system, 'username' in credentials can be Email OR Username
|
||||
formData.append('username', credentials.username || credentials.email);
|
||||
formData.append('password', credentials.password);
|
||||
|
||||
const loginResponse = await api.post('/auth/jwt/login', formData, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||
});
|
||||
|
||||
// Store the JWT token
|
||||
const token = loginResponse.data.access_token;
|
||||
if (token) {
|
||||
localStorage.setItem('access_token', token);
|
||||
}
|
||||
|
||||
// 2. Fetch User Profile
|
||||
const userResponse = await api.get<User>('/auth/users/me');
|
||||
const user = userResponse.data;
|
||||
|
||||
set({ user, isAuthenticated: true, isLoading: false, isParentalModeActive: user.role === 'parent' });
|
||||
|
||||
// 3. Auto-select profile if it's a learner
|
||||
if (user.role === 'learner' && user.learner_profile) {
|
||||
set({ activeLearner: user.learner_profile });
|
||||
localStorage.setItem('active_learner_id', user.learner_profile.id);
|
||||
} else {
|
||||
// If parent, fetch all supervised child profiles
|
||||
await get().fetchProfiles();
|
||||
}
|
||||
} catch (err: any) {
|
||||
set({ error: 'Invalid credentials', isLoading: false });
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
register: async (credentials) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
await api.post('/auth/register', {
|
||||
email: credentials.email,
|
||||
password: credentials.password,
|
||||
role: 'parent'
|
||||
});
|
||||
// Auto-login after register would happen here if desired
|
||||
set({ isLoading: false });
|
||||
} catch (err: any) {
|
||||
set({ error: err.response?.data?.detail || 'Registration failed', isLoading: false });
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
localStorage.removeItem('access_token');
|
||||
set({ user: null, isAuthenticated: false, activeLearner: null });
|
||||
},
|
||||
|
||||
checkAuth: async (refresh = false) => {
|
||||
if (!refresh) set({ isLoading: true });
|
||||
try {
|
||||
const userResponse = await api.get<User>('/auth/users/me');
|
||||
const user = userResponse.data;
|
||||
set({ user, isAuthenticated: true, isParentalModeActive: user.role === 'parent' });
|
||||
|
||||
if (user.role === 'learner' && user.learner_profile) {
|
||||
set({ activeLearner: user.learner_profile });
|
||||
} else {
|
||||
// Restore profiles for parent
|
||||
const profileResponse = await api.get<LearnerProfile[]>('/auth/profiles');
|
||||
const profiles = profileResponse.data;
|
||||
set({ learnerProfiles: profiles });
|
||||
|
||||
// Restore active learner from localStorage (if parent previously selected one)
|
||||
const storedLearnerId = localStorage.getItem('active_learner_id');
|
||||
if (storedLearnerId) {
|
||||
const active = profiles.find(p => p.id === storedLearnerId);
|
||||
if (active) {
|
||||
set({ activeLearner: active });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!refresh) set({ isLoading: false });
|
||||
} catch (err) {
|
||||
set({ user: null, isAuthenticated: false, isLoading: false, activeLearner: null });
|
||||
}
|
||||
},
|
||||
|
||||
verifyParentalGate: async (pinOrPassword) => {
|
||||
try {
|
||||
const response = await api.post('/auth/verify-parental-gate', pinOrPassword);
|
||||
if (response.data.success) {
|
||||
set({ isParentalModeActive: true });
|
||||
} else {
|
||||
throw new Error(response.data.error || 'Verification failed');
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error("Parental verification failed", err);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
fetchProfiles: async () => {
|
||||
try {
|
||||
const response = await api.get<LearnerProfile[]>('/auth/profiles');
|
||||
set({ learnerProfiles: response.data });
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch profiles", err);
|
||||
}
|
||||
},
|
||||
|
||||
selectProfile: async (learnerId) => {
|
||||
try {
|
||||
const response = await api.post(`/auth/select-profile/${learnerId}`);
|
||||
if (response.data.success) {
|
||||
set({ activeLearner: response.data.profile });
|
||||
// Store in local storage to persist choice
|
||||
localStorage.setItem('active_learner_id', learnerId);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to select profile", err);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
updateProfile: async (data: { first_name?: string; avatar_url?: string }) => {
|
||||
try {
|
||||
const response = await api.patch('/auth/profiles/me', data);
|
||||
const updatedProfile = response.data;
|
||||
|
||||
set((state) => ({
|
||||
user: state.user ? {
|
||||
...state.user,
|
||||
learner_profile: state.user.role === 'learner' ? updatedProfile : state.user.learner_profile
|
||||
} : null,
|
||||
activeLearner: state.activeLearner?.id === updatedProfile.id ? updatedProfile : state.activeLearner,
|
||||
learnerProfiles: state.learnerProfiles.map(p => p.id === updatedProfile.id ? updatedProfile : p)
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error("Failed to update profile", err);
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
createChildAccount: async (data) => {
|
||||
try {
|
||||
const response = await api.post('/auth/profiles', data);
|
||||
if (response.data.success) {
|
||||
// Refresh profiles
|
||||
await get().fetchProfiles();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to create child account", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}));
|
||||
Reference in new issue
Block a user