Initial Commit

This commit is contained in:
Eric Gullickson
2025-09-17 16:09:15 -05:00
parent 0cdb9803de
commit a052040e3a
373 changed files with 437090 additions and 6773 deletions

View File

@@ -0,0 +1,24 @@
import { create } from 'zustand';
import { Vehicle } from '../../features/vehicles/types/vehicles.types';
interface AppState {
// UI state
sidebarOpen: boolean;
selectedVehicle: Vehicle | null;
// Actions
toggleSidebar: () => void;
setSidebarOpen: (open: boolean) => void;
setSelectedVehicle: (vehicle: Vehicle | null) => void;
}
export const useAppStore = create<AppState>((set) => ({
// Initial state
sidebarOpen: false,
selectedVehicle: null,
// Actions
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setSidebarOpen: (open: boolean) => set({ sidebarOpen: open }),
setSelectedVehicle: (vehicle: Vehicle | null) => set({ selectedVehicle: vehicle }),
}));

View File

@@ -1,54 +1,12 @@
/**
* @ai-summary Global state management with Zustand
* @ai-context Minimal global state, features manage their own state
*/
// Export navigation store
export { useNavigationStore } from './navigation';
export type { MobileScreen, VehicleSubScreen } from './navigation';
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
// Export user store
export { useUserStore } from './user';
interface User {
id: string;
email: string;
name?: string;
}
// Export app store (compatibility)
export { useAppStore } from './app';
interface AppState {
// User state
user: User | null;
setUser: (user: User | null) => void;
// UI state
sidebarOpen: boolean;
toggleSidebar: () => void;
// Selected vehicle (for context)
selectedVehicleId: string | null;
setSelectedVehicle: (id: string | null) => void;
}
export const useAppStore = create<AppState>()(
devtools(
persist(
(set) => ({
// User state
user: null,
setUser: (user) => set({ user }),
// UI state
sidebarOpen: true,
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
// Selected vehicle
selectedVehicleId: null,
setSelectedVehicle: (vehicleId) => set({ selectedVehicleId: vehicleId }),
}),
{
name: 'motovaultpro-storage',
partialize: (state) => ({
selectedVehicleId: state.selectedVehicleId,
sidebarOpen: state.sidebarOpen,
}),
}
)
)
);
// Note: This replaces any existing store exports and provides
// centralized access to all Zustand stores in the application

View File

@@ -0,0 +1,205 @@
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
export type MobileScreen = 'Dashboard' | 'Vehicles' | 'Log Fuel' | 'Settings';
export type VehicleSubScreen = 'list' | 'detail' | 'add' | 'edit';
interface NavigationHistory {
screen: MobileScreen;
vehicleSubScreen?: VehicleSubScreen;
selectedVehicleId?: string | null;
timestamp: number;
metadata?: Record<string, any>;
}
interface FormState {
data: Record<string, any>;
timestamp: number;
isDirty: boolean;
}
interface NavigationState {
// Current navigation state
activeScreen: MobileScreen;
vehicleSubScreen: VehicleSubScreen;
selectedVehicleId: string | null;
// Navigation history for back button
navigationHistory: NavigationHistory[];
// Form state preservation
formStates: Record<string, FormState>;
// Loading and error states
isNavigating: boolean;
navigationError: string | null;
// Actions
navigateToScreen: (screen: MobileScreen, metadata?: Record<string, any>) => void;
navigateToVehicleSubScreen: (subScreen: VehicleSubScreen, vehicleId?: string, metadata?: Record<string, any>) => void;
goBack: () => boolean;
canGoBack: () => boolean;
saveFormState: (formId: string, data: any, isDirty?: boolean) => void;
restoreFormState: (formId: string) => FormState | null;
clearFormState: (formId: string) => void;
clearAllFormStates: () => void;
setNavigationError: (error: string | null) => void;
}
export const useNavigationStore = create<NavigationState>()(
persist(
(set, get) => ({
// Initial state
activeScreen: 'Vehicles',
vehicleSubScreen: 'list',
selectedVehicleId: null,
navigationHistory: [],
formStates: {},
isNavigating: false,
navigationError: null,
// Navigation actions
navigateToScreen: (screen, metadata = {}) => {
const currentState = get();
// Skip navigation if already on the same screen
if (currentState.activeScreen === screen && !currentState.isNavigating) {
return;
}
try {
const historyEntry: NavigationHistory = {
screen: currentState.activeScreen,
vehicleSubScreen: currentState.vehicleSubScreen,
selectedVehicleId: currentState.selectedVehicleId,
timestamp: Date.now(),
metadata,
};
// Update state atomically to prevent blank screens
set({
activeScreen: screen,
vehicleSubScreen: screen === 'Vehicles' ? currentState.vehicleSubScreen : 'list',
selectedVehicleId: screen === 'Vehicles' ? currentState.selectedVehicleId : null,
navigationHistory: [...currentState.navigationHistory, historyEntry].slice(-10),
isNavigating: false,
navigationError: null,
});
} catch (error) {
set({
navigationError: error instanceof Error ? error.message : 'Navigation failed',
isNavigating: false
});
}
},
navigateToVehicleSubScreen: (subScreen, vehicleId, metadata = {}) => {
const currentState = get();
set({ isNavigating: true, navigationError: null });
try {
const historyEntry: NavigationHistory = {
screen: currentState.activeScreen,
vehicleSubScreen: currentState.vehicleSubScreen,
selectedVehicleId: currentState.selectedVehicleId,
timestamp: Date.now(),
metadata,
};
set({
vehicleSubScreen: subScreen,
selectedVehicleId: vehicleId !== null ? vehicleId : currentState.selectedVehicleId,
navigationHistory: [...currentState.navigationHistory, historyEntry].slice(-10),
isNavigating: false,
});
} catch (error) {
set({
navigationError: error instanceof Error ? error.message : 'Navigation failed',
isNavigating: false
});
}
},
goBack: () => {
const currentState = get();
const lastEntry = currentState.navigationHistory[currentState.navigationHistory.length - 1];
if (lastEntry) {
set({
activeScreen: lastEntry.screen,
vehicleSubScreen: lastEntry.vehicleSubScreen || 'list',
selectedVehicleId: lastEntry.selectedVehicleId,
navigationHistory: currentState.navigationHistory.slice(0, -1),
isNavigating: false,
navigationError: null,
});
return true;
}
return false;
},
canGoBack: () => {
return get().navigationHistory.length > 0;
},
// Form state management
saveFormState: (formId, data, isDirty = true) => {
const currentState = get();
const formState: FormState = {
data,
timestamp: Date.now(),
isDirty,
};
set({
formStates: {
...currentState.formStates,
[formId]: formState,
},
});
},
restoreFormState: (formId) => {
const state = get().formStates[formId];
const maxAge = 2 * 60 * 60 * 1000; // 2 hours
if (state && Date.now() - state.timestamp < maxAge) {
return state;
}
// Clean up old state
if (state) {
get().clearFormState(formId);
}
return null;
},
clearFormState: (formId) => {
const currentState = get();
const newFormStates = { ...currentState.formStates };
delete newFormStates[formId];
set({ formStates: newFormStates });
},
clearAllFormStates: () => {
set({ formStates: {} });
},
setNavigationError: (error) => {
set({ navigationError: error });
},
}),
{
name: 'motovaultpro-mobile-navigation',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
activeScreen: state.activeScreen,
vehicleSubScreen: state.vehicleSubScreen,
selectedVehicleId: state.selectedVehicleId,
formStates: state.formStates,
}),
}
)
);

View File

@@ -0,0 +1,101 @@
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface UserPreferences {
unitSystem: 'imperial' | 'metric';
darkMode: boolean;
notifications: {
email: boolean;
push: boolean;
maintenance: boolean;
};
}
interface UserState {
// User data (persisted subset)
userProfile: {
id: string;
name: string;
email: string;
picture: string;
} | null;
preferences: UserPreferences;
// Session data (not persisted)
isOnline: boolean;
lastSyncTimestamp: number;
// Actions
setUserProfile: (profile: any) => void;
updatePreferences: (preferences: Partial<UserPreferences>) => void;
setOnlineStatus: (isOnline: boolean) => void;
updateLastSync: () => void;
clearUserData: () => void;
}
export const useUserStore = create<UserState>()(
persist(
(set) => ({
// Initial state
userProfile: null,
preferences: {
unitSystem: 'imperial',
darkMode: false,
notifications: {
email: true,
push: true,
maintenance: true,
},
},
isOnline: true,
lastSyncTimestamp: 0,
// Actions
setUserProfile: (profile) => {
if (profile) {
set({
userProfile: {
id: profile.sub,
name: profile.name,
email: profile.email,
picture: profile.picture,
},
});
}
},
updatePreferences: (newPreferences) => {
set((state) => ({
preferences: { ...state.preferences, ...newPreferences },
}));
},
setOnlineStatus: (isOnline) => set({ isOnline }),
updateLastSync: () => set({ lastSyncTimestamp: Date.now() }),
clearUserData: () => set({
userProfile: null,
preferences: {
unitSystem: 'imperial',
darkMode: false,
notifications: {
email: true,
push: true,
maintenance: true,
},
},
}),
}),
{
name: 'motovaultpro-user-context',
storage: createJSONStorage(() => localStorage),
partialize: (state) => ({
userProfile: state.userProfile,
preferences: state.preferences,
// Don't persist session data
}),
}
)
);