diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index de0942e0..ee7fc15d 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -4,6 +4,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import AppRoutes from './routes'; import { store } from './store/store'; import { initializeAuthPersistence } from './services/authService'; +import AuthInitializer from './features/auth/AuthInitializer'; // Initialize the QueryClient const queryClient = new QueryClient(); @@ -13,13 +14,16 @@ initializeAuthPersistence(); /** * Root Application Component. - * Wraps the app with Redux Provider and React Query Provider. + * Wraps the app with Redux Provider, React Query Provider, and AuthInitializer. + * AuthInitializer ensures auth state is restored from persistence before routes are rendered. */ const App: React.FC = () => { return ( - + + + ); diff --git a/apps/web/src/features/auth/AuthInitializer.tsx b/apps/web/src/features/auth/AuthInitializer.tsx new file mode 100644 index 00000000..2b527826 --- /dev/null +++ b/apps/web/src/features/auth/AuthInitializer.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { checkAuthStatus } from './authSlice'; +import type { RootState, AppDispatch } from '../../store/store'; + +interface AuthInitializerProps { + children: React.ReactNode; +} + +/** + * AuthInitializer Component + * Initializes authentication state from Firebase persistence on app load + * Shows a loading screen until the initial auth check is complete + * This prevents premature redirect to login before the persisted session is restored + */ +const AuthInitializer: React.FC = ({ children }) => { + const dispatch = useDispatch(); + const { isInitialized } = useSelector((state: RootState) => state.auth); + + useEffect(() => { + // Perform initial auth check when component mounts + // This restores the persisted session from Firebase + dispatch(checkAuthStatus()); + }, [dispatch]); + + // Show loading state while initializing auth + if (!isInitialized) { + return ( +
+
+
+
+
+

Loading application...

+
+
+ ); + } + + return <>{children}; +}; + +export default AuthInitializer; diff --git a/apps/web/src/features/auth/authSlice.ts b/apps/web/src/features/auth/authSlice.ts index b30154d2..0d525534 100644 --- a/apps/web/src/features/auth/authSlice.ts +++ b/apps/web/src/features/auth/authSlice.ts @@ -17,6 +17,7 @@ interface AuthState { isAuthenticated: boolean; status: "idle" | "loading" | "succeeded" | "failed"; error: string | null; + isInitialized: boolean; // Track whether initial auth check has completed } const initialState: AuthState = { @@ -24,6 +25,7 @@ const initialState: AuthState = { isAuthenticated: false, status: "idle", error: null, + isInitialized: false, // Start as false until initial auth check completes }; /** @@ -154,6 +156,9 @@ const authSlice = createSlice({ // Check auth status thunk builder + .addCase(checkAuthStatus.pending, (state) => { + state.status = "loading"; + }) .addCase(checkAuthStatus.fulfilled, (state, action) => { if (action.payload) { state.user = action.payload; @@ -163,6 +168,11 @@ const authSlice = createSlice({ state.isAuthenticated = false; } state.status = "idle"; + state.isInitialized = true; // Mark initialization as complete + }) + .addCase(checkAuthStatus.rejected, (state) => { + state.isInitialized = true; // Mark initialization as complete even on error + state.isAuthenticated = false; }); }, }); diff --git a/apps/web/src/features/workforce/directory/EditStaff.tsx b/apps/web/src/features/workforce/directory/EditStaff.tsx index ff5b72e3..49b34a94 100644 --- a/apps/web/src/features/workforce/directory/EditStaff.tsx +++ b/apps/web/src/features/workforce/directory/EditStaff.tsx @@ -13,6 +13,7 @@ export default function EditStaff() { const [staff, setStaff] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); + const [isEditing, setIsEditing] = useState(false); useEffect(() => { const fetchStaff = async () => { @@ -41,7 +42,8 @@ export default function EditStaff() { setIsSubmitting(true); try { await workforceService.entities.Staff.update(id, staffData); - navigate("/staff"); + setStaff(staffData); + setIsEditing(false); } catch (error) { console.error("Failed to update staff", error); } finally { @@ -49,6 +51,10 @@ export default function EditStaff() { } }; + const handleCancel = () => { + setIsEditing(false); + }; + if (isLoading) { return (
@@ -60,7 +66,7 @@ export default function EditStaff() { return ( {staff && ( - +
+ {!isEditing && ( +
+ +
+ )} + {isEditing && ( +
+ + +
+ )} + +
)}
); diff --git a/apps/web/src/features/workforce/directory/components/StaffForm.tsx b/apps/web/src/features/workforce/directory/components/StaffForm.tsx index cbe2cfb7..dbc5095f 100644 --- a/apps/web/src/features/workforce/directory/components/StaffForm.tsx +++ b/apps/web/src/features/workforce/directory/components/StaffForm.tsx @@ -7,9 +7,9 @@ import { Button } from "@/common/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/common/components/ui/select"; import { Checkbox } from "@/common/components/ui/checkbox"; import { - Save, Loader2, User, Activity, MapPin, - Calendar, ChevronRight, FileText, - Shield, Star, Info + Save, Loader2, User, FileText, Briefcase, + Calendar, ChevronRight, Shield, Star, Info, + Mail, Phone, MapPin, Award, Clock, AlertCircle } from "lucide-react"; import { AnimatePresence } from "framer-motion"; import type { Staff } from "../../type"; @@ -19,12 +19,13 @@ interface StaffFormProps { staff?: Staff; onSubmit: (data: Omit) => Promise; isSubmitting: boolean; + disabled?: boolean; } -type TabType = "general" | "performance" | "location" | "additional"; +type TabType = "overview" | "documents" | "work_history" | "compliance"; -export default function StaffForm({ staff, onSubmit, isSubmitting }: StaffFormProps) { - const [activeTab, setActiveTab] = useState("general"); +export default function StaffForm({ staff, onSubmit, isSubmitting, disabled = false }: StaffFormProps) { + const [activeTab, setActiveTab] = useState("overview"); const { register, handleSubmit, control, reset } = useForm({ defaultValues: staff || { @@ -55,7 +56,7 @@ export default function StaffForm({ staff, onSubmit, isSubmitting }: StaffFormPr action: "", notes: "", accounting_comments: "", - rating: 0, + averageRating: 0, shift_coverage_percentage: 100, cancellation_count: 0, no_show_count: 0, @@ -75,10 +76,10 @@ export default function StaffForm({ staff, onSubmit, isSubmitting }: StaffFormPr }; const tabs: { id: TabType; label: string; icon: React.ReactNode }[] = [ - { id: "general", label: "General Info", icon: }, - { id: "performance", label: "Performance", icon: }, - { id: "location", label: "Location", icon: }, - { id: "additional", label: "Other", icon: }, + { id: "overview", label: "Overview", icon: }, + { id: "documents", label: "Documents", icon: }, + { id: "work_history", label: "Work History", icon: }, + { id: "compliance", label: "Compliance", icon: }, ]; return ( @@ -104,7 +105,7 @@ export default function StaffForm({ staff, onSubmit, isSubmitting }: StaffFormPr
-

+

Quick Save

@@ -125,22 +126,22 @@ export default function StaffForm({ staff, onSubmit, isSubmitting }: StaffFormPr {/* Content Area */}
- {activeTab === "general" && ( + {activeTab === "overview" && ( } - className="p-8 grid grid-cols-1 md:grid-cols-2 gap-8" + className="p-8 space-y-8" footer={
-

Next Step: Performance

-

Define reliability and coverage metrics.

+

Next Step: Documents

+

Upload and manage staff documentation.

} > -
- - + {/* Basic Information */} +
+

+ + BASIC INFORMATION +

+
+
+ + +
+ +
+ + +
+ +
+ + } + className="font-medium" + placeholder="j.doe@example.com" + /> +
+ +
+ + } + className="font-medium" + placeholder="+1 (555) 000-0000" + /> +
+
-
- - + {/* Skills & Position */} +
+

+ + SKILLS & POSITION +

+
+
+ + ( + + )} + /> +
+ +
+ + +
+ +
+ + ( + + )} + /> +
+ +
+ + ( + + )} + /> +
+
-
- - ( - - )} - /> + {/* Rating & Performance Overview */} +
+

+ + RATING & PERFORMANCE +

+
+
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+
-
- - ( - - )} - /> -
+ {/* Location & Department */} +
+

+ + LOCATION & DEPARTMENT +

+
+
+ + ( + + )} + /> +
-
- - } - className="font-medium" - placeholder="j.doe@example.com" - /> -
+
+ + +
-
- - -
+
+ + +
-
- - ( - - )} - /> -
- -
- - ( - - )} - /> +
+ + ( + + )} + /> +
+
)} - {activeTab === "performance" && ( + {activeTab === "documents" && ( } + id="documents" + title="Staff Documents" + icon={} className="p-8 space-y-8" > -
+ {/* Document Upload Section */} +
+

+ + REQUIRED DOCUMENTS +

-
- - +
+
+ + + Pending + +
+

Employment eligibility verification

- -
+
+
+ + + Pending + +
+

Tax withholding information

+
+
+
+ + {/* Certifications */} +
+

+ + CERTIFICATIONS +

-
- - +
+
+ + + Valid + +
+

Expires: 12/31/2026

- -
-
-
- - -
- -
-
- -
-
- - -
-
- - -
-
- -
-
- ( - field.onChange(e.target.checked)} - className="w-6 h-6 rounded-lg border-2 border-primary/20 data-[state=checked]:bg-primary data-[state=checked]:border-primary" - /> - )} - /> -
- -

Mark this member as verified for automatic invoicing.

+
+
+ + +
+

Add any additional certifications or licenses

- - )} - {activeTab === "location" && ( - } - className="p-8 grid grid-cols-1 md:grid-cols-2 gap-8" - > + {/* Notes */}
- - ( - - )} - /> -
- -
- - -
- -
- - -
- -
- -