ADD new frontend
This commit is contained in:
263
frontend/src/stores/auth.store.ts
Normal file
263
frontend/src/stores/auth.store.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
role: 'admin' | 'manager' | 'baker' | 'staff';
|
||||
permissions: string[];
|
||||
tenantId: string;
|
||||
tenantName: string;
|
||||
avatar?: string;
|
||||
lastLogin?: string;
|
||||
preferences?: {
|
||||
language: string;
|
||||
timezone: string;
|
||||
theme: 'light' | 'dark';
|
||||
notifications: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
// State
|
||||
user: User | null;
|
||||
token: string | null;
|
||||
refreshToken: string | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
|
||||
// Actions
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
logout: () => void;
|
||||
refreshAuth: () => Promise<void>;
|
||||
updateUser: (updates: Partial<User>) => void;
|
||||
clearError: () => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
|
||||
// Permission helpers
|
||||
hasPermission: (permission: string) => boolean;
|
||||
hasRole: (role: string) => boolean;
|
||||
canAccess: (resource: string, action: string) => boolean;
|
||||
}
|
||||
|
||||
// Mock API functions (replace with actual API calls)
|
||||
const mockLogin = async (email: string, password: string): Promise<{ user: User; token: string; refreshToken: string }> => {
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
if (email === 'admin@bakery.com' && password === 'admin') {
|
||||
return {
|
||||
user: {
|
||||
id: '1',
|
||||
email: 'admin@bakery.com',
|
||||
name: 'Admin User',
|
||||
role: 'admin',
|
||||
permissions: ['*'],
|
||||
tenantId: 'tenant-1',
|
||||
tenantName: 'Panadería San Miguel',
|
||||
avatar: undefined,
|
||||
lastLogin: new Date().toISOString(),
|
||||
preferences: {
|
||||
language: 'es',
|
||||
timezone: 'Europe/Madrid',
|
||||
theme: 'light',
|
||||
notifications: true,
|
||||
},
|
||||
},
|
||||
token: 'mock-jwt-token',
|
||||
refreshToken: 'mock-refresh-token',
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('Credenciales inválidas');
|
||||
};
|
||||
|
||||
const mockRefreshToken = async (refreshToken: string): Promise<{ token: string; refreshToken: string }> => {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
if (refreshToken === 'mock-refresh-token') {
|
||||
return {
|
||||
token: 'new-mock-jwt-token',
|
||||
refreshToken: 'new-mock-refresh-token',
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('Invalid refresh token');
|
||||
};
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
// Initial state
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
// Actions
|
||||
login: async (email: string, password: string) => {
|
||||
try {
|
||||
set({ isLoading: true, error: null });
|
||||
|
||||
const response = await mockLogin(email, password);
|
||||
|
||||
set({
|
||||
user: response.user,
|
||||
token: response.token,
|
||||
refreshToken: response.refreshToken,
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
} catch (error) {
|
||||
set({
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
error: error instanceof Error ? error.message : 'Error de autenticación',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
set({
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
},
|
||||
|
||||
refreshAuth: async () => {
|
||||
try {
|
||||
const { refreshToken } = get();
|
||||
if (!refreshToken) {
|
||||
throw new Error('No refresh token available');
|
||||
}
|
||||
|
||||
set({ isLoading: true });
|
||||
|
||||
const response = await mockRefreshToken(refreshToken);
|
||||
|
||||
set({
|
||||
token: response.token,
|
||||
refreshToken: response.refreshToken,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
} catch (error) {
|
||||
set({
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
error: error instanceof Error ? error.message : 'Error al renovar sesión',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
updateUser: (updates: Partial<User>) => {
|
||||
const { user } = get();
|
||||
if (user) {
|
||||
set({
|
||||
user: { ...user, ...updates },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
clearError: () => {
|
||||
set({ error: null });
|
||||
},
|
||||
|
||||
setLoading: (loading: boolean) => {
|
||||
set({ isLoading: loading });
|
||||
},
|
||||
|
||||
// Permission helpers
|
||||
hasPermission: (permission: string): boolean => {
|
||||
const { user } = get();
|
||||
if (!user) return false;
|
||||
|
||||
// Admin has all permissions
|
||||
if (user.permissions.includes('*')) return true;
|
||||
|
||||
return user.permissions.includes(permission);
|
||||
},
|
||||
|
||||
hasRole: (role: string): boolean => {
|
||||
const { user } = get();
|
||||
return user?.role === role;
|
||||
},
|
||||
|
||||
canAccess: (resource: string, action: string): boolean => {
|
||||
const { user, hasPermission } = get();
|
||||
if (!user) return false;
|
||||
|
||||
// Check specific permission
|
||||
if (hasPermission(`${resource}:${action}`)) return true;
|
||||
|
||||
// Check wildcard permissions
|
||||
if (hasPermission(`${resource}:*`)) return true;
|
||||
if (hasPermission('*')) return true;
|
||||
|
||||
// Role-based access fallback
|
||||
switch (user.role) {
|
||||
case 'admin':
|
||||
return true;
|
||||
case 'manager':
|
||||
return ['inventory', 'production', 'sales', 'reports'].includes(resource);
|
||||
case 'baker':
|
||||
return ['production', 'inventory'].includes(resource) &&
|
||||
['read', 'update'].includes(action);
|
||||
case 'staff':
|
||||
return ['inventory', 'sales'].includes(resource) &&
|
||||
action === 'read';
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'auth-storage',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state) => ({
|
||||
user: state.user,
|
||||
token: state.token,
|
||||
refreshToken: state.refreshToken,
|
||||
isAuthenticated: state.isAuthenticated,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Selectors for common use cases
|
||||
export const useAuthUser = () => useAuthStore((state) => state.user);
|
||||
export const useIsAuthenticated = () => useAuthStore((state) => state.isAuthenticated);
|
||||
export const useAuthLoading = () => useAuthStore((state) => state.isLoading);
|
||||
export const useAuthError = () => useAuthStore((state) => state.error);
|
||||
export const usePermissions = () => useAuthStore((state) => ({
|
||||
hasPermission: state.hasPermission,
|
||||
hasRole: state.hasRole,
|
||||
canAccess: state.canAccess,
|
||||
}));
|
||||
|
||||
// Hook for auth actions
|
||||
export const useAuthActions = () => useAuthStore((state) => ({
|
||||
login: state.login,
|
||||
logout: state.logout,
|
||||
refreshAuth: state.refreshAuth,
|
||||
updateUser: state.updateUser,
|
||||
clearError: state.clearError,
|
||||
setLoading: state.setLoading,
|
||||
}));
|
||||
Reference in New Issue
Block a user