diff --git a/frontend/src/app/ModuleRouteGuard.tsx b/frontend/src/app/ModuleRouteGuard.tsx index a6e0ee1..804a324 100644 --- a/frontend/src/app/ModuleRouteGuard.tsx +++ b/frontend/src/app/ModuleRouteGuard.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from 'react'; import { Suspense } from 'react'; import { useLocation } from 'react-router-dom'; import { useShellOutletContext } from '@/app/shellOutletContext'; -import { StatePanel } from '@/components/ui/state-panel'; +import { PageSkeleton } from '@/components/ui/page-skeleton'; import { canUserRoleAccessModuleRoute } from '@/shared/constants/moduleRoutes'; import NotFound from '@/pages/NotFound'; @@ -20,13 +20,7 @@ export function ModuleRouteGuard({ children }: ModuleRouteGuardProps) { } return ( - - Loading module... - - )} - > + }> {children} ); diff --git a/frontend/src/components/AppLayout.tsx b/frontend/src/components/AppLayout.tsx index 49a20e8..4b9a3fe 100644 --- a/frontend/src/components/AppLayout.tsx +++ b/frontend/src/components/AppLayout.tsx @@ -4,12 +4,11 @@ import { useAuth } from '@/contexts/useAuth'; import { useIsMobile } from '@/hooks/use-mobile'; import { useAppShell } from '@/business/app-shell/hooks'; import { AppFooter } from '@/components/app-shell/AppFooter'; +import { AppShellSkeleton } from '@/components/ui/app-shell-skeleton'; import Sidebar from '@/components/frameworks/Sidebar'; import TopBar from '@/components/frameworks/TopBar'; -import { Loader2 } from 'lucide-react'; - const AppLayout: React.FC = () => { const { profile, loading: authLoading } = useAuth(); const isMobile = useIsMobile(); @@ -22,19 +21,8 @@ const AppLayout: React.FC = () => { setMobileSidebarOpen, } = useAppShell({ profile, isMobile }); - // Loading state if (authLoading) { - return ( -
-
-
- F -
- -

Loading FRAMEworks...

-
-
- ); + return ; } return ( diff --git a/frontend/src/components/dashboard/DashboardHero.tsx b/frontend/src/components/dashboard/DashboardHero.tsx index 2ae8e6c..d541dcc 100644 --- a/frontend/src/components/dashboard/DashboardHero.tsx +++ b/frontend/src/components/dashboard/DashboardHero.tsx @@ -1,8 +1,22 @@ +import { useMemo } from 'react'; import { Sparkles } from 'lucide-react'; import type { FrameEntryViewModel } from '@/business/frame/types'; import { HERO_IMAGE } from '@/shared/constants/appData'; +function getCurrentWeekMonday(): string { + const today = new Date(); + const dayOfWeek = today.getDay(); + const mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; + const monday = new Date(today); + monday.setDate(today.getDate() + mondayOffset); + return monday.toLocaleDateString('en-US', { + month: 'long', + day: 'numeric', + year: 'numeric', + }); +} + interface DashboardHeroProps { readonly greeting: string; readonly userName: string; @@ -12,8 +26,8 @@ interface DashboardHeroProps { export function DashboardHero({ greeting, userName, - latestFrame, }: DashboardHeroProps) { + const currentWeekMonday = useMemo(() => getCurrentWeekMonday(), []); return (
Classroom @@ -23,7 +37,7 @@ export function DashboardHero({
- {latestFrame ? `Week of ${latestFrame.weekOf}` : 'No weekly focus posted'} + Week of {currentWeekMonday}

diff --git a/frontend/src/components/sidebar/SidebarBrand.tsx b/frontend/src/components/sidebar/SidebarBrand.tsx index 3ce1bf6..4cb238c 100644 --- a/frontend/src/components/sidebar/SidebarBrand.tsx +++ b/frontend/src/components/sidebar/SidebarBrand.tsx @@ -4,7 +4,7 @@ interface SidebarBrandProps { export function SidebarBrand({ collapsed }: SidebarBrandProps) { return ( -
+
{collapsed ? (
F diff --git a/frontend/src/components/sidebar/SidebarView.tsx b/frontend/src/components/sidebar/SidebarView.tsx index 4779715..ccb5acb 100644 --- a/frontend/src/components/sidebar/SidebarView.tsx +++ b/frontend/src/components/sidebar/SidebarView.tsx @@ -2,9 +2,7 @@ import { ChevronLeft, ChevronRight } from 'lucide-react'; import type { SidebarPage } from '@/business/app-shell/types'; import { SidebarBrand } from '@/components/sidebar/SidebarBrand'; -import { SidebarCampusRolePanel } from '@/components/sidebar/SidebarCampusRolePanel'; import { SidebarNavigation } from '@/components/sidebar/SidebarNavigation'; -import { Button } from '@/components/ui/button'; interface SidebarViewProps { readonly page: SidebarPage; @@ -15,16 +13,14 @@ export function SidebarView({ page }: SidebarViewProps) { ); } diff --git a/frontend/src/components/top-bar/TopBarView.tsx b/frontend/src/components/top-bar/TopBarView.tsx index 0b7eedf..78f78b9 100644 --- a/frontend/src/components/top-bar/TopBarView.tsx +++ b/frontend/src/components/top-bar/TopBarView.tsx @@ -16,7 +16,7 @@ interface TopBarViewProps { export function TopBarView({ page }: TopBarViewProps) { return ( -
+
+ ); +} diff --git a/frontend/src/components/ui/page-skeleton.tsx b/frontend/src/components/ui/page-skeleton.tsx new file mode 100644 index 0000000..4e1578d --- /dev/null +++ b/frontend/src/components/ui/page-skeleton.tsx @@ -0,0 +1,42 @@ +import { Skeleton } from '@/components/ui/skeleton'; + +/** + * Generic page loading skeleton. Shows a content-shaped placeholder + * while the page component is being lazy-loaded. + */ +export function PageSkeleton() { + return ( +
+ {/* Page header */} +
+
+ + +
+ +
+ + {/* Content cards grid */} +
+ {[1, 2, 3].map((i) => ( +
+ + + + +
+ ))} +
+ + {/* Additional content area */} +
+ +
+ + + +
+
+
+ ); +}