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": {