Start integrating the onboarding flow with backend 1
This commit is contained in:
@@ -1,359 +0,0 @@
|
||||
import React, { createContext, useContext, useReducer, useEffect, ReactNode } from 'react';
|
||||
|
||||
export interface Bakery {
|
||||
id: string;
|
||||
name: string;
|
||||
logo?: string;
|
||||
role: 'owner' | 'manager' | 'baker' | 'staff';
|
||||
status: 'active' | 'inactive';
|
||||
address?: string;
|
||||
description?: string;
|
||||
settings?: {
|
||||
timezone: string;
|
||||
currency: string;
|
||||
language: string;
|
||||
};
|
||||
permissions?: string[];
|
||||
}
|
||||
|
||||
interface BakeryState {
|
||||
bakeries: Bakery[];
|
||||
currentBakery: Bakery | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
type BakeryAction =
|
||||
| { type: 'SET_LOADING'; payload: boolean }
|
||||
| { type: 'SET_ERROR'; payload: string | null }
|
||||
| { type: 'SET_BAKERIES'; payload: Bakery[] }
|
||||
| { type: 'SET_CURRENT_BAKERY'; payload: Bakery }
|
||||
| { type: 'ADD_BAKERY'; payload: Bakery }
|
||||
| { type: 'UPDATE_BAKERY'; payload: { id: string; updates: Partial<Bakery> } }
|
||||
| { type: 'REMOVE_BAKERY'; payload: string };
|
||||
|
||||
const initialState: BakeryState = {
|
||||
bakeries: [],
|
||||
currentBakery: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
// Mock bakeries data
|
||||
const mockBakeries: Bakery[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Panadería San Miguel',
|
||||
logo: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=100&h=100&fit=crop&crop=center',
|
||||
role: 'owner',
|
||||
status: 'active',
|
||||
address: 'Calle Mayor 123, Madrid, 28013',
|
||||
description: 'Panadería tradicional familiar con más de 30 años de experiencia.',
|
||||
settings: {
|
||||
timezone: 'Europe/Madrid',
|
||||
currency: 'EUR',
|
||||
language: 'es',
|
||||
},
|
||||
permissions: ['*'],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Panadería La Artesana',
|
||||
logo: 'https://images.unsplash.com/photo-1509440159596-0249088772ff?w=100&h=100&fit=crop&crop=center',
|
||||
role: 'manager',
|
||||
status: 'active',
|
||||
address: 'Avenida de la Paz 45, Barcelona, 08001',
|
||||
description: 'Panadería artesanal especializada en panes tradicionales catalanes.',
|
||||
settings: {
|
||||
timezone: 'Europe/Madrid',
|
||||
currency: 'EUR',
|
||||
language: 'ca',
|
||||
},
|
||||
permissions: ['inventory:*', 'production:*', 'sales:*', 'reports:read'],
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Pan y Masa',
|
||||
logo: 'https://images.unsplash.com/photo-1524704654690-b56c05c78a00?w=100&h=100&fit=crop&crop=center',
|
||||
role: 'baker',
|
||||
status: 'active',
|
||||
address: 'Plaza Central 8, Valencia, 46001',
|
||||
description: 'Panadería moderna con enfoque en productos orgánicos.',
|
||||
settings: {
|
||||
timezone: 'Europe/Madrid',
|
||||
currency: 'EUR',
|
||||
language: 'es',
|
||||
},
|
||||
permissions: ['production:*', 'inventory:read', 'inventory:update'],
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Horno Dorado',
|
||||
role: 'staff',
|
||||
status: 'inactive',
|
||||
address: 'Calle del Sol 67, Sevilla, 41001',
|
||||
description: 'Panadería tradicional andaluza.',
|
||||
settings: {
|
||||
timezone: 'Europe/Madrid',
|
||||
currency: 'EUR',
|
||||
language: 'es',
|
||||
},
|
||||
permissions: ['inventory:read', 'sales:read'],
|
||||
},
|
||||
];
|
||||
|
||||
function bakeryReducer(state: BakeryState, action: BakeryAction): BakeryState {
|
||||
switch (action.type) {
|
||||
case 'SET_LOADING':
|
||||
return { ...state, isLoading: action.payload };
|
||||
|
||||
case 'SET_ERROR':
|
||||
return { ...state, error: action.payload, isLoading: false };
|
||||
|
||||
case 'SET_BAKERIES':
|
||||
return {
|
||||
...state,
|
||||
bakeries: action.payload,
|
||||
currentBakery: state.currentBakery || action.payload[0] || null,
|
||||
isLoading: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
case 'SET_CURRENT_BAKERY':
|
||||
// Save to localStorage for persistence
|
||||
localStorage.setItem('selectedBakery', action.payload.id);
|
||||
return { ...state, currentBakery: action.payload };
|
||||
|
||||
case 'ADD_BAKERY':
|
||||
return {
|
||||
...state,
|
||||
bakeries: [...state.bakeries, action.payload]
|
||||
};
|
||||
|
||||
case 'UPDATE_BAKERY':
|
||||
return {
|
||||
...state,
|
||||
bakeries: state.bakeries.map(bakery =>
|
||||
bakery.id === action.payload.id
|
||||
? { ...bakery, ...action.payload.updates }
|
||||
: bakery
|
||||
),
|
||||
currentBakery: state.currentBakery?.id === action.payload.id
|
||||
? { ...state.currentBakery, ...action.payload.updates }
|
||||
: state.currentBakery
|
||||
};
|
||||
|
||||
case 'REMOVE_BAKERY':
|
||||
const remainingBakeries = state.bakeries.filter(b => b.id !== action.payload);
|
||||
return {
|
||||
...state,
|
||||
bakeries: remainingBakeries,
|
||||
currentBakery: state.currentBakery?.id === action.payload
|
||||
? remainingBakeries[0] || null
|
||||
: state.currentBakery
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
interface BakeryContextType extends BakeryState {
|
||||
selectBakery: (bakery: Bakery) => void;
|
||||
addBakery: (bakery: Omit<Bakery, 'id'>) => Promise<void>;
|
||||
updateBakery: (id: string, updates: Partial<Bakery>) => Promise<void>;
|
||||
removeBakery: (id: string) => Promise<void>;
|
||||
refreshBakeries: () => Promise<void>;
|
||||
hasPermission: (permission: string) => boolean;
|
||||
canAccess: (resource: string, action: string) => boolean;
|
||||
}
|
||||
|
||||
const BakeryContext = createContext<BakeryContextType | undefined>(undefined);
|
||||
|
||||
interface BakeryProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function BakeryProvider({ children }: BakeryProviderProps) {
|
||||
const [state, dispatch] = useReducer(bakeryReducer, initialState);
|
||||
|
||||
// Load bakeries on mount
|
||||
useEffect(() => {
|
||||
loadBakeries();
|
||||
}, []);
|
||||
|
||||
// Load saved bakery selection
|
||||
useEffect(() => {
|
||||
if (state.bakeries.length > 0 && !state.currentBakery) {
|
||||
const savedBakeryId = localStorage.getItem('selectedBakery');
|
||||
const savedBakery = savedBakeryId
|
||||
? state.bakeries.find(b => b.id === savedBakeryId)
|
||||
: null;
|
||||
|
||||
if (savedBakery) {
|
||||
dispatch({ type: 'SET_CURRENT_BAKERY', payload: savedBakery });
|
||||
} else if (state.bakeries[0]) {
|
||||
dispatch({ type: 'SET_CURRENT_BAKERY', payload: state.bakeries[0] });
|
||||
}
|
||||
}
|
||||
}, [state.bakeries, state.currentBakery]);
|
||||
|
||||
const loadBakeries = async () => {
|
||||
try {
|
||||
dispatch({ type: 'SET_LOADING', payload: true });
|
||||
|
||||
// Simulate API call delay
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// In a real app, this would be an API call
|
||||
dispatch({ type: 'SET_BAKERIES', payload: mockBakeries });
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: error instanceof Error ? error.message : 'Error loading bakeries'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const selectBakery = (bakery: Bakery) => {
|
||||
dispatch({ type: 'SET_CURRENT_BAKERY', payload: bakery });
|
||||
};
|
||||
|
||||
const addBakery = async (bakeryData: Omit<Bakery, 'id'>) => {
|
||||
try {
|
||||
dispatch({ type: 'SET_LOADING', payload: true });
|
||||
|
||||
// Simulate API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const newBakery: Bakery = {
|
||||
...bakeryData,
|
||||
id: Date.now().toString(), // In real app, this would come from the API
|
||||
};
|
||||
|
||||
dispatch({ type: 'ADD_BAKERY', payload: newBakery });
|
||||
dispatch({ type: 'SET_LOADING', payload: false });
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: error instanceof Error ? error.message : 'Error adding bakery'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const updateBakery = async (id: string, updates: Partial<Bakery>) => {
|
||||
try {
|
||||
dispatch({ type: 'SET_LOADING', payload: true });
|
||||
|
||||
// Simulate API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
dispatch({ type: 'UPDATE_BAKERY', payload: { id, updates } });
|
||||
dispatch({ type: 'SET_LOADING', payload: false });
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: error instanceof Error ? error.message : 'Error updating bakery'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const removeBakery = async (id: string) => {
|
||||
try {
|
||||
dispatch({ type: 'SET_LOADING', payload: true });
|
||||
|
||||
// Simulate API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
dispatch({ type: 'REMOVE_BAKERY', payload: id });
|
||||
dispatch({ type: 'SET_LOADING', payload: false });
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
payload: error instanceof Error ? error.message : 'Error removing bakery'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const refreshBakeries = async () => {
|
||||
await loadBakeries();
|
||||
};
|
||||
|
||||
const hasPermission = (permission: string): boolean => {
|
||||
if (!state.currentBakery) return false;
|
||||
|
||||
const permissions = state.currentBakery.permissions || [];
|
||||
|
||||
// Admin/owner has all permissions
|
||||
if (permissions.includes('*')) return true;
|
||||
|
||||
return permissions.includes(permission);
|
||||
};
|
||||
|
||||
const canAccess = (resource: string, action: string): boolean => {
|
||||
if (!state.currentBakery) return false;
|
||||
|
||||
// Check specific permission
|
||||
if (hasPermission(`${resource}:${action}`)) return true;
|
||||
|
||||
// Check wildcard permissions
|
||||
if (hasPermission(`${resource}:*`)) return true;
|
||||
|
||||
// Role-based access fallback
|
||||
switch (state.currentBakery.role) {
|
||||
case 'owner':
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
const value: BakeryContextType = {
|
||||
...state,
|
||||
selectBakery,
|
||||
addBakery,
|
||||
updateBakery,
|
||||
removeBakery,
|
||||
refreshBakeries,
|
||||
hasPermission,
|
||||
canAccess,
|
||||
};
|
||||
|
||||
return (
|
||||
<BakeryContext.Provider value={value}>
|
||||
{children}
|
||||
</BakeryContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useBakery(): BakeryContextType {
|
||||
const context = useContext(BakeryContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useBakery must be used within a BakeryProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
// Convenience hooks
|
||||
export const useCurrentBakery = () => {
|
||||
const { currentBakery } = useBakery();
|
||||
return currentBakery;
|
||||
};
|
||||
|
||||
export const useBakeries = () => {
|
||||
const { bakeries } = useBakery();
|
||||
return bakeries;
|
||||
};
|
||||
|
||||
export const useBakeryPermissions = () => {
|
||||
const { hasPermission, canAccess } = useBakery();
|
||||
return { hasPermission, canAccess };
|
||||
};
|
||||
Reference in New Issue
Block a user