From ea68e660fc8f0eb9d424a6d0471c87fbfaef3205 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 6 Feb 2026 20:53:48 +0000 Subject: [PATCH] login UI --- .../v2/design-elements/BasePage.tsx | 21 +++++- .../v2/design-elements/TextLink.tsx | 42 +++++++++++ .../Components/v2/design-elements/index.tsx | 1 + client/src/Hooks/useLoginForm.ts | 13 ++++ client/src/Pages/Auth/Login/index.tsx | 70 ++++++++++++++++++- client/src/Validation/login.ts | 12 ++++ client/src/locales/en.json | 13 +++- 7 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 client/src/Components/v2/design-elements/TextLink.tsx create mode 100644 client/src/Hooks/useLoginForm.ts create mode 100644 client/src/Validation/login.ts diff --git a/client/src/Components/v2/design-elements/BasePage.tsx b/client/src/Components/v2/design-elements/BasePage.tsx index b0f5ee26f..65e74b553 100644 --- a/client/src/Components/v2/design-elements/BasePage.tsx +++ b/client/src/Components/v2/design-elements/BasePage.tsx @@ -205,12 +205,17 @@ export const MonitorBasePageWithStates = ({ ); }; -interface BaseAuthPageProps extends React.PropsWithChildren { +interface BaseAuthPageProps extends React.PropsWithChildren, StackProps { title: string; subtitle: string; } -export const BaseAuthPage = ({ title, subtitle, children }: BaseAuthPageProps) => { +export const BaseAuthPage = ({ + title, + subtitle, + children, + ...props +}: BaseAuthPageProps) => { const theme = useTheme(); return ( {subtitle} - {children} + + {children} + ); }; diff --git a/client/src/Components/v2/design-elements/TextLink.tsx b/client/src/Components/v2/design-elements/TextLink.tsx new file mode 100644 index 000000000..704b4b841 --- /dev/null +++ b/client/src/Components/v2/design-elements/TextLink.tsx @@ -0,0 +1,42 @@ +import Typography from "@mui/material/Typography"; +import Stack from "@mui/material/Stack"; +import type { StackProps } from "@mui/material/Stack"; +import Link from "@mui/material/Link"; +import { Link as RouterLink } from "react-router-dom"; + +import { useTheme } from "@mui/material/styles"; + +interface TextLinkProps extends StackProps { + text: string; + linkText: string; + href: string; + target?: string; +} + +export const TextLink = ({ + text, + linkText, + href, + target = "_self", + ...props +}: TextLinkProps) => { + const theme = useTheme(); + + return ( + + {text} + + {linkText} + + + ); +}; diff --git a/client/src/Components/v2/design-elements/index.tsx b/client/src/Components/v2/design-elements/index.tsx index a96fea052..b7b1d484e 100644 --- a/client/src/Components/v2/design-elements/index.tsx +++ b/client/src/Components/v2/design-elements/index.tsx @@ -18,3 +18,4 @@ export * from "./BaseChart"; export * from "./Gauge"; export * from "./Tabs"; export * from "./SplitBox"; +export * from "./TextLink"; diff --git a/client/src/Hooks/useLoginForm.ts b/client/src/Hooks/useLoginForm.ts new file mode 100644 index 000000000..d69acd0ab --- /dev/null +++ b/client/src/Hooks/useLoginForm.ts @@ -0,0 +1,13 @@ +import { useMemo } from "react"; +import { loginSchema, type LoginFormData } from "@/Validation/login"; + +export const useLoginForm = () => { + return useMemo(() => { + const defaults: LoginFormData = { + email: "", + password: "", + }; + + return { schema: loginSchema, defaults }; + }, []); +}; diff --git a/client/src/Pages/Auth/Login/index.tsx b/client/src/Pages/Auth/Login/index.tsx index 5756f18cb..9c945f7ed 100644 --- a/client/src/Pages/Auth/Login/index.tsx +++ b/client/src/Pages/Auth/Login/index.tsx @@ -1,13 +1,79 @@ -import { BaseAuthPage } from "@/Components/v2/design-elements"; +import { BaseAuthPage, TextLink } from "@/Components/v2/design-elements"; +import { Button, TextField } from "@/Components/v2/inputs"; import { useTranslation } from "react-i18next"; +import { useForm, Controller } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod/dist/zod.js"; +import { useLoginForm } from "@/Hooks/useLoginForm"; +import type { LoginFormData } from "@/Validation/login"; + const LoginPage = () => { const { t } = useTranslation(); + + const { schema, defaults } = useLoginForm(); + + const { control, handleSubmit } = useForm({ + resolver: zodResolver(schema), + defaultValues: defaults, + }); + + const onSubmit = (data: LoginFormData) => { + console.log("Login submitted:", data); + }; + return ( + > + ( + + )} + /> + ( + + )} + /> + + + + ); }; diff --git a/client/src/Validation/login.ts b/client/src/Validation/login.ts new file mode 100644 index 000000000..03eb8d4e2 --- /dev/null +++ b/client/src/Validation/login.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +export const loginSchema = z.object({ + email: z + .string() + .min(1, "auth.common.inputs.email.errors.empty") + .email("auth.common.inputs.email.errors.invalid") + .transform((val) => val.toLowerCase().trim()), + password: z.string().min(1, "auth.common.inputs.password.errors.empty"), +}); + +export type LoginFormData = z.infer; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index a803fad00..514ac3db4 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -515,7 +515,18 @@ }, "login": { "title": "Welcome back to Checkmate!", - "subtitle": "Log in to continue" + "subtitle": "Log in to continue", + "submit": "Login", + "links": { + "forgotPassword": { + "text": "Forgot password?", + "linkText": "Reset password" + }, + "register": { + "text": "Don't have an account?", + "linkText": "Register here" + } + } } }, "checks": {