All files / src/theme ThemeContext.jsx

100% Statements 37/37
80% Branches 16/20
100% Functions 11/11
100% Lines 33/33

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 94 95 96 97 98 99 100 101 102 103 104                  6x   6x 27x 27x 27x   40x 40x   27x   27x 27x 24x   3x 2x   1x     27x 27x   27x 22x                 27x 3x     27x 1x                               27x 13x 6x 27x   6x 6x 6x     7x 7x     27x                                             24x  
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
import { ThemeProvider, StyledEngineProvider } from "@mui/material/styles";
import { Box, CssBaseline } from "@mui/material";
import { useSelector } from "react-redux";
import { createUiTheme } from "@/theme/ui";
import RTL from "@/theme/ui/options/right-to-left";
import { themeIDs } from "./CustomColors";
import { getPortalRole } from "@/authentication/auth0";
 
const ThemeToggleContext = createContext();
 
export const ThemeToggleProvider = ({ children }) => {
    const [themeMode, setThemeMode] = useState("light");
    const [themePreset, setThemePreset] = useState("default");
    const [themeDirection] = useState("ltr");
 
    const teacherThemeState = useSelector((state) => state?.TeacherThemes || null);
    const studentThemeState = useSelector((state) => state?.StudentThemes || null);
 
    const role = getPortalRole();
 
    const reduxThemeState = (() => {
        if (role === "teacher") {
            return teacherThemeState;
        }
        if (role === "student") {
            return studentThemeState;
        }
        return null;
    })();
 
    const safeThemeMode = themeMode || "light";
    const safeThemePreset = themePreset || "default";
 
    const theme = useMemo(
        () =>
            createUiTheme({
                mode: safeThemeMode,
                preset: safeThemePreset,
                direction: themeDirection,
            }),
        [safeThemeMode, safeThemePreset, themeDirection]
    );
 
    const toggleThemeMode = () => {
        setThemeMode((prevMode) => (prevMode === "light" ? "dark" : "light"));
    };
 
    const changeThemePreset = (newPreset) => {
        setThemePreset(newPreset);
    };
 
    // Modified by Allan Ninal — this effect had no else branch, so clearing the saved
    // theme left whatever was already applied on screen. `_deleteTeacherTheme.fulfilled`
    // nulls both fields precisely so this provider can fall back ("slice clears local
    // cache so ThemeContext defaults", in the slice), but nothing here did. Reset while
    // in a dark saved theme and the app said "Theme reverted to default" and stayed
    // dark, disagreeing with the server until the next reload. ThemePresets calls
    // changeThemePreset("default") itself, which hid half of it — the preset reverted,
    // the mode did not.
    //
    // A hand-picked mode is not at risk from the plain else: this only runs when the
    // dependencies CHANGE, and toggling by hand does not touch the slice. I first
    // guarded it with a "was a theme ever applied" ref, then removed it — no realistic
    // transition could tell the two apart, so it was complexity no test could justify.
    useEffect(() => {
        if (reduxThemeState?.selectedTheme && reduxThemeState?.colorMode) {
            const themeId = reduxThemeState.selectedTheme._id;
            const mappedPreset = Object.entries(themeIDs).find(([, id]) => id === themeId)?.[0] || "default";
 
            setThemeMode(reduxThemeState.colorMode);
            setThemePreset(mappedPreset);
            return;
        }
 
        setThemeMode("light");
        setThemePreset("default");
    }, [reduxThemeState?.selectedTheme, reduxThemeState?.colorMode]);
 
    return (
        <ThemeToggleContext.Provider value={{ themeMode, toggleThemeMode, themePreset, changeThemePreset }}>
            <StyledEngineProvider injectFirst>
                <ThemeProvider theme={theme}>
                    <RTL themeDirection={themeDirection}>
                        <CssBaseline enableColorScheme />
                        <Box
                            sx={{
                                transition: "background-color 0.3s ease, color 0.3s ease",
                                bgcolor: "background.default",
                                color: "text.primary",
                                minHeight: "100%",
                            }}
                        >
                            {children}
                        </Box>
                    </RTL>
                </ThemeProvider>
            </StyledEngineProvider>
        </ThemeToggleContext.Provider>
    );
};
 
export const useThemeToggle = () => useContext(ThemeToggleContext);