Imporve the role based forntend protected roles

This commit is contained in:
Urtzi Alfaro
2025-09-09 07:32:59 +02:00
parent ddb75f8e55
commit 5269a083b6
15 changed files with 286 additions and 91 deletions

View File

@@ -1,12 +1,14 @@
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { tenantService, type TenantResponse } from '../api';
import { tenantService, type TenantResponse, type TenantAccessResponse } from '../api';
import { useAuthUser } from './auth.store';
import { TENANT_ROLES, GLOBAL_USER_ROLES } from '../types/roles';
export interface TenantState {
// State
currentTenant: TenantResponse | null;
availableTenants: TenantResponse[] | null;
currentTenantAccess: TenantAccessResponse | null;
isLoading: boolean;
error: string | null;
@@ -14,6 +16,7 @@ export interface TenantState {
setCurrentTenant: (tenant: TenantResponse) => void;
switchTenant: (tenantId: string) => Promise<boolean>;
loadUserTenants: () => Promise<void>;
loadCurrentTenantAccess: () => Promise<void>;
clearTenants: () => void;
clearError: () => void;
setLoading: (loading: boolean) => void;
@@ -29,14 +32,17 @@ export const useTenantStore = create<TenantState>()(
// Initial state
currentTenant: null,
availableTenants: null,
currentTenantAccess: null,
isLoading: false,
error: null,
// Actions
setCurrentTenant: (tenant: TenantResponse) => {
set({ currentTenant: tenant });
set({ currentTenant: tenant, currentTenantAccess: null });
// Update API client with new tenant ID
tenantService.setCurrentTenant(tenant);
// Load tenant access info
get().loadCurrentTenantAccess();
},
switchTenant: async (tenantId: string): Promise<boolean> => {
@@ -116,10 +122,25 @@ export const useTenantStore = create<TenantState>()(
}
},
loadCurrentTenantAccess: async (): Promise<void> => {
try {
const { currentTenant } = get();
if (!currentTenant) return;
const accessInfo = await tenantService.getCurrentUserTenantAccess(currentTenant.id);
set({ currentTenantAccess: accessInfo });
} catch (error) {
// Don't set error state for access loading failures - just log
console.warn('Failed to load tenant access:', error);
set({ currentTenantAccess: null });
}
},
clearTenants: () => {
set({
currentTenant: null,
availableTenants: null,
currentTenantAccess: null,
error: null,
});
tenantService.clearCurrentTenant();
@@ -135,33 +156,34 @@ export const useTenantStore = create<TenantState>()(
// Permission helpers (migrated from BakeryContext)
hasPermission: (permission: string): boolean => {
const { currentTenant } = get();
if (!currentTenant) return false;
const { currentTenant, currentTenantAccess } = get();
if (!currentTenant || !currentTenantAccess || !currentTenantAccess.has_access) {
return false;
}
// Get user to determine role within this tenant
const authState = JSON.parse(localStorage.getItem('auth-storage') || '{}')?.state;
const user = authState?.user;
// Check if user has specific permission in their tenant permissions array
if (currentTenantAccess.permissions?.includes(permission)) {
return true;
}
// Admin role has all permissions
if (user?.role === 'admin') return true;
// Check if user has broader permissions that include this one
if (currentTenantAccess.permissions?.includes('*') ||
currentTenantAccess.permissions?.includes('admin')) {
return true;
}
// TODO: Implement proper tenant-based permissions
// For now, use basic role-based permissions
switch (user?.role) {
case 'admin':
// Role-based fallback for common permissions based on tenant role
const tenantRole = currentTenantAccess.role;
switch (tenantRole) {
case TENANT_ROLES.OWNER:
case TENANT_ROLES.ADMIN:
return true;
case 'manager':
return ['inventory', 'production', 'sales', 'reports'].some(resource =>
permission.startsWith(resource)
);
case 'baker':
return ['production', 'inventory'].some(resource =>
permission.startsWith(resource)
) && !permission.includes(':delete');
case 'staff':
return ['inventory', 'sales'].some(resource =>
permission.startsWith(resource)
) && permission.includes(':read');
case TENANT_ROLES.MEMBER:
// Members can read and write but not delete or manage users
return !permission.includes('delete') && !permission.includes('admin');
case TENANT_ROLES.VIEWER:
// Viewers can only read
return permission.includes('read') || permission.includes('view');
default:
return false;
}
@@ -185,6 +207,7 @@ export const useTenantStore = create<TenantState>()(
partialize: (state) => ({
currentTenant: state.currentTenant,
availableTenants: state.availableTenants,
currentTenantAccess: state.currentTenantAccess,
}),
onRehydrateStorage: () => (state) => {
// Initialize API client with stored tenant when store rehydrates
@@ -201,6 +224,7 @@ export const useTenantStore = create<TenantState>()(
// Selectors for common use cases
export const useCurrentTenant = () => useTenantStore((state) => state.currentTenant);
export const useAvailableTenants = () => useTenantStore((state) => state.availableTenants);
export const useCurrentTenantAccess = () => useTenantStore((state) => state.currentTenantAccess);
export const useTenantLoading = () => useTenantStore((state) => state.isLoading);
export const useTenantError = () => useTenantStore((state) => state.error);
@@ -209,6 +233,7 @@ export const useTenantActions = () => useTenantStore((state) => ({
setCurrentTenant: state.setCurrentTenant,
switchTenant: state.switchTenant,
loadUserTenants: state.loadUserTenants,
loadCurrentTenantAccess: state.loadCurrentTenantAccess,
clearTenants: state.clearTenants,
clearError: state.clearError,
setLoading: state.setLoading,
@@ -224,6 +249,7 @@ export const useTenantPermissions = () => useTenantStore((state) => ({
export const useTenant = () => {
const currentTenant = useCurrentTenant();
const availableTenants = useAvailableTenants();
const currentTenantAccess = useCurrentTenantAccess();
const isLoading = useTenantLoading();
const error = useTenantError();
const actions = useTenantActions();
@@ -232,6 +258,7 @@ export const useTenant = () => {
return {
currentTenant,
availableTenants,
currentTenantAccess,
isLoading,
error,
...actions,