base page, auth header, language and theme switches

This commit is contained in:
Alex Holliday
2026-02-06 20:32:10 +00:00
parent 1c390a8baf
commit 527543d79f
10 changed files with 291 additions and 20 deletions
@@ -1,4 +1,6 @@
import Logo from "@/assets/icons/checkmate-icon.svg?react";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import Alert from "@mui/material/Alert";
import Link from "@mui/material/Link";
import {
@@ -9,6 +11,7 @@ import {
} from "@/Components/v2/design-elements/Fallback";
import { Breadcrumb } from "@/Components/v2/design-elements/Breadcrumb";
import CircularProgress from "@mui/material/CircularProgress";
import { HeaderAuthControls } from "@/Pages/Auth/components/HeaderAuthControls";
import type { StackProps } from "@mui/material/Stack";
import { useTheme } from "@mui/material/styles";
@@ -201,3 +204,46 @@ export const MonitorBasePageWithStates = ({
</BasePage>
);
};
interface BaseAuthPageProps extends React.PropsWithChildren {
title: string;
subtitle: string;
}
export const BaseAuthPage = ({ title, subtitle, children }: BaseAuthPageProps) => {
const theme = useTheme();
return (
<BasePage
breadcrumbOverride={[]}
gap={theme.spacing(8)}
alignItems={"center"}
justifyContent={"center"}
minHeight="100vh"
position={"relative"}
>
<HeaderAuthControls
hideLogo
py={theme.spacing(4)}
position={"absolute"}
top={0}
left={0}
/>
<Box width={{ xs: 60, sm: 70, md: 80 }}>
<Logo
width={"100%"}
height={"100%"}
/>
</Box>
<Stack alignItems={"center"}>
<Typography
variant="h1"
mb={theme.spacing(2)}
>
{title}
</Typography>
<Typography variant="h1">{subtitle}</Typography>
</Stack>
{children}
</BasePage>
);
};
@@ -0,0 +1,100 @@
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import MenuItem from "@mui/material/MenuItem";
import { Select } from "@/Components/v2/inputs";
import "flag-icons/css/flag-icons.min.css";
import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material";
import { useSelector } from "react-redux";
import { useDispatch } from "react-redux";
import { setLanguage } from "@/Features/UI/uiSlice";
import type { RootState } from "@/Types/state";
const langMap: Record<string, string> = {
cs: "cz",
ja: "jp",
uk: "ua",
vi: "vn",
};
export const LanguageSelector = () => {
const { i18n } = useTranslation();
const theme = useTheme();
const { language = "en" } = useSelector((state: RootState) => state.ui);
const dispatch = useDispatch();
const handleChange = (event: any) => {
const newLang = event.target.value;
dispatch(setLanguage(newLang));
};
const languages = Object.keys(i18n.options.resources || {});
return (
<Select
value={language}
onChange={handleChange}
size="small"
// sx={{
// minWidth: 80,
// "& .MuiSelect-select": {
// display: "flex",
// alignItems: "center",
// justifyContent: "center",
// },
// "& .MuiSelect-icon": {
// alignSelf: "center",
// },
// }}
>
{languages.map((lang) => {
let parsedLang = lang === "en" ? "gb" : lang;
if (parsedLang.includes("-")) {
parsedLang = parsedLang.split("-")[1].toLowerCase();
}
parsedLang = langMap[parsedLang] || parsedLang;
const flag = parsedLang ? `fi fi-${parsedLang}` : null;
return (
<MenuItem
key={lang}
value={lang}
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Stack
direction="row"
spacing={theme.spacing(2)}
alignItems="center"
justifyContent="center"
>
<Box
component="span"
sx={{
display: "flex",
alignItems: "center",
}}
>
{flag && <span className={flag} />}
</Box>
<Box
component="span"
sx={{ textTransform: "uppercase" }}
>
{lang}
</Box>
</Stack>
</MenuItem>
);
})}
</Select>
);
};
export default LanguageSelector;
@@ -0,0 +1,48 @@
import IconButton from "@mui/material/IconButton";
import { Moon, Sun } from "lucide-react";
import { setMode } from "@/Features/UI/uiSlice.js";
import { useTheme } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import type { RootState } from "@/Types/state";
export const SwitchTheme = () => {
const mode = useSelector((state: RootState) => state.ui.mode);
const dispatch = useDispatch();
const theme = useTheme();
const handleChange = () => {
dispatch(setMode(mode === "light" ? "dark" : "light"));
};
return (
<IconButton
id="theme-toggle"
onClick={handleChange}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
"& svg": {
transition: "stroke 0.2s ease",
},
"&:hover svg path, &:hover svg line, &:hover svg polyline, &:hover svg rect, &:hover svg circle":
{
stroke: theme.palette.primary.main,
},
}}
>
{mode === "light" ? (
<Moon
size={16}
strokeWidth={1.5}
/>
) : (
<Sun
size={16}
strokeWidth={1.5}
/>
)}
</IconButton>
);
};
@@ -16,3 +16,5 @@ export * from "./ImageUpload";
export * from "./ColorPicker";
export { DatePickerComponent as DatePicker } from "./DatePicker";
export { TimePickerComponent as TimePicker } from "./TimePicker";
export * from "./LanguageSelector";
export * from "./SwitchTheme";
-18
View File
@@ -1,18 +0,0 @@
import React from "react";
import AppBar from "@/Components/v1/Common/AppBar.jsx";
import Footer from "@/Components/v1/Common/Footer.jsx";
import { useTranslation } from "react-i18next";
const AboutUs = () => {
const { t } = useTranslation();
return (
<div>
<AppBar />
<h1>{t("aboutus")}</h1>
<Footer />
</div>
);
};
export default AboutUs;
+14
View File
@@ -0,0 +1,14 @@
import { BaseAuthPage } from "@/Components/v2/design-elements";
import { useTranslation } from "react-i18next";
const LoginPage = () => {
const { t } = useTranslation();
return (
<BaseAuthPage
title={t("pages.auth.login.title")}
subtitle={t("pages.auth.login.subtitle")}
></BaseAuthPage>
);
};
export default LoginPage;
@@ -0,0 +1,53 @@
import Stack, { type StackProps } from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
import Logo from "@/assets/icons/checkmate-icon.svg?react";
import { LanguageSelector, SwitchTheme } from "@/Components/v2/inputs";
import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next";
interface HeaderAuthControlsProps extends StackProps {
hideLogo?: boolean;
}
export const HeaderAuthControls = ({
hideLogo = false,
...stackProps
}: HeaderAuthControlsProps) => {
// Hooks
const theme = useTheme();
const { t } = useTranslation();
return (
<Stack
width={"100%"}
direction="row"
alignItems="center"
justifyContent="space-between"
px={theme.spacing(12)}
gap={theme.spacing(4)}
{...stackProps}
>
<Stack
direction="row"
alignItems="center"
gap={theme.spacing(4)}
>
{!hideLogo && (
<>
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
<Typography sx={{ userSelect: "none" }}>{t("common.appName")}</Typography>
</>
)}
</Stack>
<Stack
direction="row"
spacing={theme.spacing(2)}
alignItems="center"
>
<LanguageSelector />
<SwitchTheme />
</Stack>
</Stack>
);
};
+8 -2
View File
@@ -7,7 +7,7 @@ import { Navigate, Route, Routes as LibRoutes } from "react-router";
import HomeLayout from "@/Components/v1/Layouts/HomeLayout";
import NotFound from "../Pages/NotFound/index.jsx";
// Auth
import AuthLogin from "../Pages/Auth/Login/index.jsx";
import AuthLogin from "../Pages/Auth/Login";
import AuthRegister from "../Pages/Auth/Register/index.jsx";
import AuthForgotPassword from "../Pages/Auth/ForgotPassword.jsx";
import AuthCheckEmail from "../Pages/Auth/CheckEmail.jsx";
@@ -360,7 +360,13 @@ const Routes = () => {
<Route
path="/login"
element={<AuthLogin />}
element={
<>
<ThemeProvider theme={v2theme}>
<AuthLogin />
</ThemeProvider>
</>
}
/>
<Route
+20
View File
@@ -498,6 +498,26 @@
"now": "Now",
"os": "OS",
"pages": {
"auth": {
"common": {
"form": {
"option": {
"email": {
"label": "Email",
"placeholder": "me@example.com"
},
"password": {
"label": "Password",
"placeholder": "********"
}
}
}
},
"login": {
"title": "Welcome back to Checkmate!",
"subtitle": "Log in to continue"
}
},
"checks": {
"selects": {
"monitor": {