From bb6ab00e5bbda642e0a123b80519f3f453166fb2 Mon Sep 17 00:00:00 2001 From: Randy Dean Date: Wed, 27 May 2026 21:08:19 -0400 Subject: [PATCH] Created admin login UI component --- js/src/app/router/guards/RequireAuth.tsx | 2 +- js/src/app/router/router.tsx | 10 ++- js/src/features/admin/Admin.page.tsx | 10 +++ js/src/features/admin/AdminLogin.page.tsx | 85 +++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 js/src/features/admin/Admin.page.tsx create mode 100644 js/src/features/admin/AdminLogin.page.tsx diff --git a/js/src/app/router/guards/RequireAuth.tsx b/js/src/app/router/guards/RequireAuth.tsx index 909fe70..db3e977 100644 --- a/js/src/app/router/guards/RequireAuth.tsx +++ b/js/src/app/router/guards/RequireAuth.tsx @@ -5,7 +5,7 @@ import { Navigate, Outlet } from "react-router-dom"; /** * Route guard: render the nested routes only for an authenticated user. * While the session is loading, show a spinner; if there is no session, redirect - * to the public home (no /login page exists yet). + * to the public home. */ export function RequireAuth() { const { data: session, isPending } = useSession(); diff --git a/js/src/app/router/router.tsx b/js/src/app/router/router.tsx index 792873f..3eceb23 100644 --- a/js/src/app/router/router.tsx +++ b/js/src/app/router/router.tsx @@ -3,12 +3,17 @@ import { AppLayout } from "@/app/layouts/AppLayout"; import { PublicLayout } from "@/app/layouts/PublicLayout"; import { RequireAdmin } from "@/app/router/guards/RequireAdmin"; import { RequireAuth } from "@/app/router/guards/RequireAuth"; +import AdminPage from "@/features/admin/Admin.page"; +import AdminLoginPage from "@/features/admin/AdminLogin.page"; import HomePage from "@/features/home/Home.page"; import SamplePage from "@/features/sample/Sample.page"; import SampleAdminPage from "@/features/sample/SampleAdmin.page"; import { createBrowserRouter } from "react-router-dom"; export const router = createBrowserRouter([ + // Public admin login: its page owns the full viewport. + { path: "admin/login", element: }, + // Public: no guard, public chrome. { element: , @@ -28,7 +33,10 @@ export const router = createBrowserRouter([ children: [ { element: , - children: [{ path: "sample/admin", element: }], + children: [ + { path: "admin", element: }, + { path: "sample/admin", element: }, + ], }, ], }, diff --git a/js/src/features/admin/Admin.page.tsx b/js/src/features/admin/Admin.page.tsx new file mode 100644 index 0000000..e3c5a98 --- /dev/null +++ b/js/src/features/admin/Admin.page.tsx @@ -0,0 +1,10 @@ +import { Stack, Text, Title } from "@mantine/core"; + +export default function AdminPage() { + return ( + + Admin home + Manage PatChats administrative workflows from here. + + ); +} diff --git a/js/src/features/admin/AdminLogin.page.tsx b/js/src/features/admin/AdminLogin.page.tsx new file mode 100644 index 0000000..0b4abe8 --- /dev/null +++ b/js/src/features/admin/AdminLogin.page.tsx @@ -0,0 +1,85 @@ +import { + Box, + Button, + Flex, + PasswordInput, + Stack, + TextInput, + Title, +} from "@mantine/core"; +import { useState, type FormEvent } from "react"; + +export default function AdminLoginPage() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const [errors, setErrors] = useState({ + email: "", + password: "", + }); + + const handleSubmit = (event: FormEvent) => { + event.preventDefault(); + + const newErrors = { + email: "", + password: "", + }; + + if (!email.trim()) { + newErrors.email = "Email is required"; + } + + if (!password.trim()) { + newErrors.password = "Password is required"; + } + + setErrors(newErrors); + + if (newErrors.email || newErrors.password) { + return; + } + + // Login handler is not in scope for this ticket. + console.log("Admin login submitted", { email, password }); + }; + + return ( + + + + + PatChats + + + + + + + Admin Log in + + setEmail(event.currentTarget.value)} + placeholder="Admin email" + radius="md" + size="sm" + value={email} + /> + setPassword(event.currentTarget.value)} + placeholder="Password" + radius="md" + size="sm" + value={password} + /> + + + + + + ); +}