Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | 'use client' import { useEffect, type ReactNode } from 'react' import { SessionModeBannerProvider, useSessionModeBanner, } from '@/contexts/SessionModeBannerContext' import { useSessionMode } from '@/hooks/useSessionMode' import { ProjectingBanner } from './ProjectingBanner' // ============================================================================ // Types // ============================================================================ interface PracticeLayoutProps { /** The student/player ID for fetching session mode */ studentId: string /** Child content to render */ children: ReactNode /** Callback when banner action is triggered (e.g., open StartPracticeModal) */ onBannerAction?: () => void } // ============================================================================ // Inner Component (uses context) // ============================================================================ interface PracticeLayoutInnerProps { children: ReactNode onBannerAction?: () => void } /** * Inner component that registers the action callback and renders the banner. * Needs to be inside the provider to access context. */ function PracticeLayoutInner({ children, onBannerAction }: PracticeLayoutInnerProps) { const { setOnAction } = useSessionModeBanner() // Register the action callback useEffect(() => { if (onBannerAction) { setOnAction(onBannerAction) } }, [onBannerAction, setOnAction]) return ( <> {/* The projecting banner renders via portal to body */} <ProjectingBanner /> {children} </> ) } // ============================================================================ // Main Component // ============================================================================ /** * PracticeLayout - Wrapper component for all practice pages * * Provides: * - SessionModeBannerProvider for banner state * - ProjectingBanner for animated banner * - Session mode data fetching * * Usage: * ```tsx * <PracticeLayout * studentId={params.studentId} * onBannerAction={() => setShowModal(true)} * > * <DashboardContent /> * </PracticeLayout> * ``` */ export function PracticeLayout({ studentId, children, onBannerAction }: PracticeLayoutProps) { // Fetch session mode const { data: sessionModeData, isLoading } = useSessionMode(studentId) return ( <SessionModeBannerProvider sessionMode={sessionModeData?.sessionMode ?? null} isLoading={isLoading} > <PracticeLayoutInner onBannerAction={onBannerAction}>{children}</PracticeLayoutInner> </SessionModeBannerProvider> ) } export default PracticeLayout |