File "layout.tsx"

Full Path: /home/trinadezambia/public_html/student_panel/src/app/(dashboard)/student/layout.tsx
File size: 8.77 KB
MIME-type: text/x-java
Charset: utf-8

'use client';

import { ReactNode, useEffect, useState } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import { useSelector, useDispatch } from 'react-redux';
import NProgress from 'nprogress';
import StudentSidebar from '@/components/ui/pages/dashboard/StudentSidebar';
import StudentHeader from '@/components/ui/pages/dashboard/StudentHeader';
import ElectiveSubjectsModal from '@/components/ui/pages/dashboard/ElectiveSubjectsModal';
import {
  SidebarProvider,
  useSidebar,
} from '@/components/contexts/SidebarContext';
import { useLanguage } from '@/components/hooks/useLanguage';
import { RootState } from '@/components/store';
import { useElectiveSubjects } from '@/components/hooks/useElectiveSubjects';
import StudentFooter from '@/components/ui/pages/dashboard/StudentFooter';
import {
  useGetSchoolSettings,
  useGetStudentProfile,
} from '@/lib/api/student/queryHooks';
import PushNotificationLayout from '@/lib/firebase/PushNotification';
import { updateUserProfile } from '@/components/store/slices/studentAuthSlice';

interface StudentLayoutProps {
  children: ReactNode;
}

function StudentLayoutContent({ children }: StudentLayoutProps) {
  const router = useRouter();
  const pathname = usePathname();

  // Track if component has mounted on client to avoid hydration mismatch
  const [mounted, setMounted] = useState(false);

  // Initialize language from Redux store
  // This hook will rehydrate language from localStorage and load translations
  const { languages, currentLanguage } = useLanguage();

  // Get current language object to check if it's RTL
  const currentLangObj = languages.find(
    (lang) => lang.code === currentLanguage,
  );

  // Get authentication state from Redux
  const { isAuthenticated, loading } = useSelector(
    (state: RootState) => state.studentAuth,
  );

  // Fetch school settings for dynamic colors and other settings
  const { data: schoolSettings } = useGetSchoolSettings();

  // Get school code from Redux state
  const { schoolCode } = useSelector((state: RootState) => state.studentAuth);

  // Fetch student profile to keep Redux state in sync with server
  const { data: profileData } = useGetStudentProfile(schoolCode);

  const dispatch = useDispatch();

  // Sync profile data to Redux when it changes
  useEffect(() => {
    if (profileData?.data && profileData.success) {
      dispatch(updateUserProfile(profileData.data));
    }
  }, [profileData, dispatch]);

  // Elective subjects modal logic
  const {
    showModal,
    setShowModal,
    electiveSubjectGroups,
    handleSaveElectiveSubjects,
  } = useElectiveSubjects();

  const {
    collapsed: sidebarCollapsed,
    setCollapsed: setSidebarCollapsed,
    isMobile,
    setIsMobile,
    isMobileDrawerOpen,
    setIsMobileDrawerOpen,
  } = useSidebar();

  // Set mounted state on client side only
  useEffect(() => {
    setMounted(true);
  }, []);

  // Apply RTL direction for Arabic language
  useEffect(() => {
    if (typeof document !== 'undefined' && currentLangObj) {
      // Set direction on HTML element
      document.documentElement.dir = currentLangObj.isRtl ? 'rtl' : 'ltr';

      // Also set lang attribute for accessibility
      document.documentElement.lang = currentLanguage;
    }
  }, [currentLangObj, currentLanguage]);

  // Apply dynamic colors from school settings API
  // This updates CSS variables on the document root when API data is available
  useEffect(() => {
    if (typeof document !== 'undefined' && schoolSettings?.data?.settings) {
      const settings = schoolSettings.data.settings;

      // Update primary color CSS variable if available from API
      if (settings.primary_color) {
        document.documentElement.style.setProperty(
          '--primary-color',
          settings.primary_color,
        );
      }

      // Update secondary color CSS variable if available from API
      if (settings.secondary_color) {
        document.documentElement.style.setProperty(
          '--secondary-color',
          settings.secondary_color,
        );
      }
    }
  }, [schoolSettings]);

  // Show/hide NProgress based on loading state
  useEffect(() => {
    if (loading) {
      NProgress.start();
    } else {
      NProgress.done();
    }

    // Cleanup on unmount
    return () => {
      NProgress.done();
    };
  }, [loading]);

  // Check for account deactivation and redirect to inactive page
  // This runs before authentication check to ensure immediate redirect
  useEffect(() => {
    if (mounted && typeof window !== 'undefined') {
      const isDeactivated = localStorage.getItem('account_deactivated');
      const deactivationMessage = localStorage.getItem('deactivation_message');

      if (isDeactivated === 'true') {
        // Redirect to inactive page with message
        const message =
          deactivationMessage ||
          'Your account is inactive. Contact school administrator for further information';
        const encodedMessage = encodeURIComponent(message);
        router.replace(`/student/auth/inactive?message=${encodedMessage}`);
      }
    }
  }, [mounted, router]);

  // Redirect unauthenticated users to login page
  useEffect(() => {
    if (mounted && !isAuthenticated && !loading) {
      router.replace('/student/auth/login');
    }
  }, [mounted, isAuthenticated, loading, router]);

  // Check if device is mobile
  useEffect(() => {
    const checkMobile = () => {
      setIsMobile(window.innerWidth < 1024);
    };

    checkMobile();
    window.addEventListener('resize', checkMobile);

    return () => window.removeEventListener('resize', checkMobile);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Handle menu click for both mobile and desktop
  const handleMenuClick = () => {
    if (isMobile) {
      setIsMobileDrawerOpen(!isMobileDrawerOpen);
    } else {
      setSidebarCollapsed(!sidebarCollapsed);
    }
  };

  // Close mobile drawer when clicking outside
  const handleOverlayClick = () => {
    if (isMobile) {
      setIsMobileDrawerOpen(false);
    }
  };

  // Show loading state during initial mount to prevent hydration mismatch
  // This ensures server and client render the same HTML initially
  if (!mounted) {
    return <div className="min-h-screen bg-gray-50" suppressHydrationWarning />;
  }

  // Don't render dashboard if user is not authenticated
  // The redirect will happen via useEffect
  if (!isAuthenticated) {
    return <div className="min-h-screen bg-gray-50" suppressHydrationWarning />;
  }

  // Minimal layout mode: hide sidebar/header/footer for full-screen pages
  // We use this for the online exam detail flow to match the provided UI
  const isMinimalLayout = pathname?.startsWith('/student/exams/online/');

  if (isMinimalLayout) {
    // Full-screen minimal layout for online exam pages:
    // Use light gray page background; individual page sections can override to white.
    return (
      <div className="min-h-screen" suppressHydrationWarning>
        {children}
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-gray-50 flex flex-col overflow-hidden!">
      {/* Mobile Overlay - only render on client after mount */}
      {isMobile && isMobileDrawerOpen && (
        <div
          className="fixed inset-0 bg-black bg-opacity-50 z-40"
          onClick={handleOverlayClick}
        />
      )}

      {/* Sidebar */}
      <StudentSidebar
        collapsed={sidebarCollapsed}
        isMobile={isMobile}
        isOpen={isMobileDrawerOpen}
        onClose={() => setIsMobileDrawerOpen(false)}
      />

      {/* Main Content - Responsive margins for tablet and desktop */}
      {/* Using explicit LTR/RTL variants for proper spacing */}
      <div
        className={`transition-all duration-300 flex-1 flex flex-col ${
          isMobile
            ? '' // Full width on mobile
            : sidebarCollapsed
              ? 'ltr:ml-14 ltr:md:ml-16 rtl:mr-14 rtl:md:mr-16' // Collapsed sidebar margin
              : 'ltr:ml-64 ltr:md:ml-76 rtl:mr-64 rtl:md:mr-76' // Expanded sidebar margin
        }`}
      >
        {/* Header - Responsive for all screen sizes */}
        <StudentHeader onMenuClick={handleMenuClick} isMobile={isMobile} />

        {/* Page Content - Responsive padding for tablets and desktop */}
        <div className="p-3 sm:p-4 lg:p-6 flex-1">{children}</div>
      </div>

      {/* Footer - mt-auto to stick to bottom */}
      <StudentFooter />

      {/* Elective Subjects Modal */}
      <ElectiveSubjectsModal
        open={showModal}
        onOpenChange={setShowModal}
        electiveSubjectGroups={electiveSubjectGroups}
        onSave={handleSaveElectiveSubjects}
      />
    </div>
  );
}

export default function StudentLayout({ children }: StudentLayoutProps) {
  return (
    <PushNotificationLayout>
      <SidebarProvider>
        <StudentLayoutContent>{children}</StudentLayoutContent>
      </SidebarProvider>
    </PushNotificationLayout>
  );
}