feat: implement registration page with form validation and add custom typography fonts

This commit is contained in:
Your Name
2026-04-10 13:41:11 +05:30
parent 97577efcf6
commit 414e683e0b
11 changed files with 199 additions and 38 deletions
+15
View File
@@ -5,6 +5,11 @@
"": {
"name": "frontend",
"dependencies": {
"@fontsource-variable/jost": "^5.2.8",
"@fontsource-variable/playfair-display": "^5.2.8",
"@fontsource-variable/playwrite-hr-lijeva": "^5.2.7",
"@fontsource/cutive-mono": "^5.2.8",
"@fontsource/knewave": "^5.2.7",
"@hookform/resolvers": "^5.2.2",
"@phosphor-icons/react": "^2.1.10",
"@tailwindcss/vite": "^4.2.2",
@@ -53,6 +58,16 @@
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
"@fontsource-variable/jost": ["@fontsource-variable/jost@5.2.8", "", {}, "sha512-+VDDHpbhgZ9A8KeHb7/LUMRR1LILVKIscNhVRSDzn3tFXoi1mFA/OhO8CZL/u2OujoGjZANjOdUZIgaxclxyjw=="],
"@fontsource-variable/playfair-display": ["@fontsource-variable/playfair-display@5.2.8", "", {}, "sha512-ZzVIXPOrL85yyOvZYoBzUszIJM+xKkHqni4IYn2CVLaGQQdJR8sBeC8yFNgjxSJ7ludTwta8qpULeOFuk5X75A=="],
"@fontsource-variable/playwrite-hr-lijeva": ["@fontsource-variable/playwrite-hr-lijeva@5.2.7", "", {}, "sha512-cQqbD8HHZDpiKdtgwUxgwAY76TC+GI9iZOxHSW0XkV/L8lA0X18z1wzR+J8yv9XZQYgLJ5WfzBGwzMSLnSLdPA=="],
"@fontsource/cutive-mono": ["@fontsource/cutive-mono@5.2.8", "", {}, "sha512-Y8PKAYfbpl9Empbb1HZBoirlj4W7RtU+G4EhvX27pHzO6RE1sO0I1ElZQH5DMCTS+MSJkMmQT33sJ0+Ji9U8eQ=="],
"@fontsource/knewave": ["@fontsource/knewave@5.2.7", "", {}, "sha512-uzx8jgcTiQgAwKvQ/hWdX7lOQPwS+K74Eij/WCVzYvAkCX7GRTnWnbxXXx0XsKR6UIN16kH/u40LW4K8aHJb1w=="],
"@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+5
View File
@@ -12,6 +12,11 @@
"preview": "vite preview"
},
"dependencies": {
"@fontsource-variable/jost": "^5.2.8",
"@fontsource-variable/playfair-display": "^5.2.8",
"@fontsource-variable/playwrite-hr-lijeva": "^5.2.7",
"@fontsource/cutive-mono": "^5.2.8",
"@fontsource/knewave": "^5.2.7",
"@hookform/resolvers": "^5.2.2",
"@phosphor-icons/react": "^2.1.10",
"@tailwindcss/vite": "^4.2.2",
+1 -1
View File
@@ -167,7 +167,7 @@
&::before,
&::after {
content: '';
content: "";
position: absolute;
top: -4.5px;
border: 5px solid transparent;
+6 -6
View File
@@ -1,8 +1,8 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import Register from './pages/Register';
import VerifyEmail from './pages/VerifyEmail';
import Activate from './pages/Activate';
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import Activate from "./pages/Activate";
import Home from "./pages/Home";
import Register from "./pages/Register";
import VerifyEmail from "./pages/VerifyEmail";
export default function App() {
return (
@@ -10,7 +10,7 @@ export default function App() {
<div className="min-h-screen bg-base-200 p-8 flex items-center justify-center">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/register" element={<Register />} />
<Route path="/onboard" element={<Register />} />
<Route path="/verify-email" element={<VerifyEmail />} />
<Route path="/activate/:uidb64/:token" element={<Activate />} />
<Route path="*" element={<Navigate to="/" replace />} />
+14
View File
@@ -0,0 +1,14 @@
import { DotIcon } from "@phosphor-icons/react"
import "@fontsource/knewave"
export default function Logo() {
return (
<div className="flex items-baseline leading-none align-baseline">
<span className="text-2xl font-light font-logo text-accent">&nbsp;Pi</span>
<DotIcon weight="fill" size={12} className="text-accent translate-y-px" />
<span className="text-2xl font-light font-logo text-accent ml-0.5">Ku</span>
<DotIcon weight="fill" size={12} className="text-accent translate-y-px" />
</div>
);
}
+17 -3
View File
@@ -14,8 +14,8 @@
--color-primary-content: #c8b890;
--color-secondary: #2a1e13;
--color-secondary-content: #e8d5b0;
--color-accent: #3d2e1e;
--color-accent-content: #c8903a;
--color-accent: #c8903a;
--color-accent-content: #3d2e1e;
--color-neutral: #2e2820;
--color-neutral-content: oklch(98% 0.016 73.684);
--color-info: #2a3040;
@@ -24,7 +24,7 @@
--color-success-content: #8aaa8a;
--color-warning: #4a3010;
--color-warning-content: #d4a050;
--color-error: #3d1e1a;
--color-error: #3d1e1f;
--color-error-content: #c07060;
--radius-selector: 1rem;
--radius-field: 0rem;
@@ -35,3 +35,17 @@
--depth: 0;
--noise: 0;
}
@theme {
--font-logo: "Knewave", system-ui;
--font-display: "Playwrite HR Lijeva Variable", cursive;
--font-sans: "Jost Variable", sans-serif;
--font-serif: "Playfair Display Variable", serif;
--color-glass-bg: rgba(35, 30, 24, 0.4);
--shadow-warm: 0 20px 50px -12px rgba(42, 30, 19, 0.5);
--radius-xl: 1.5rem;
}
.glass-card {
@apply bg-glass-bg backdrop-blur-xl border border-white/5 shadow-warm rounded-xl;
}
+12 -6
View File
@@ -1,10 +1,16 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import "@fontsource-variable/playwrite-hr-lijeva/wght.css";
import "@fontsource-variable/jost/wght.css";
import "@fontsource-variable/playfair-display/wght.css";
import App from "./App.tsx";
createRoot(document.getElementById('root')!).render(
const root = document.getElementById("root");
if (root) {
createRoot(root).render(
<StrictMode>
<App />
</StrictMode>,
)
);
}
+3 -1
View File
@@ -1,7 +1,9 @@
import Logo from "../components/Logo";
export default function Home() {
return (
<div>
<h1>Pi. Ku.</h1>
<Logo/>
</div>
);
}
+107 -2
View File
@@ -1,7 +1,112 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { InfoIcon } from "@phosphor-icons/react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import Logo from "../components/Logo";
// validation logic
const registerSchema = z
.object({
email: z.email("Please enter a valid email"),
password: z
.string()
.check(z.minLength(8, "Password must be at least 8 characters")),
confirm_password: z.string(),
})
.refine((data) => data.password === data.confirm_password, {
message: "Passwords don't match",
path: ["confirm_password"],
});
type RegisterInputs = z.infer<typeof registerSchema>;
export default function Register() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<RegisterInputs>({
resolver: zodResolver(registerSchema),
});
const onSubmit = (data: RegisterInputs) => {
console.log("Form Data:", data);
};
return (
<div>
<h1>Register</h1>
<div className="glass-card w-full max-w-sm p-2 transition-all duration-500 hover:shadow-2xl">
<form onSubmit={handleSubmit(onSubmit)} className="card-body gap-4">
<h2 className="card-title font-display text-2xl font-bold justify-center text-primary tracking-tight">
Create a <Logo /> Account
</h2>
<div className="form-control">
<label htmlFor="email" className="label font-bold font-display py-1">
Email
</label>
<input
{...register("email")}
type="email"
placeholder="you@email.com"
className={`input input-bordered focus:input-primary ${errors.email ? "input-error" : ""}`}
/>
{errors.email && (
<p className="text-error-content text-xs mt-1">{errors.email.message}</p>
)}
</div>
<div className="form-control">
<label htmlFor="password" className="label font-bold font-display py-1">
Password
</label>
<input
{...register("password")}
type="password"
placeholder="••••••••"
className={`input input-bordered focus:input-primary ${errors.password ? "input-error" : ""}`}
/>
{errors.password && (
<p className="text-error-content text-xs mt-1">{errors.password.message}</p>
)}
</div>
{/* Confirm Pass */}
<div className="form-control">
<label htmlFor="confirm_password" className="label font-bold font-display py-1">
Confirm Password
</label>
<input
{...register("confirm_password")}
type="password"
placeholder="••••••••"
className={`input input-bordered focus:input-primary ${errors.confirm_password ? "input-error" : ""}`}
/>
{errors.confirm_password && (
<p className="text-error-content text-xs mt-1">
{errors.confirm_password.message}
</p>
)}
</div>
{/* Warning */}
<div className="bg-warning/10 border-l-2 border-warning p-3 rounded-r-md flex gap-2">
<InfoIcon
size={20}
weight="duotone"
className="text-warning mt-0.5 shrink-0"
/>
<p className="text-sm text-warning-content font-medium opacity-90">
Choose a password you won't forget. <br />
<span className="font-semibold underline">There is no reset.</span> If you lose it, your letters cannot be recovered.
</p>
</div>
<div className="card-actions mt-4">
<button type="submit" className="btn btn-primary w-full shadow-lg">
Register
</button>
</div>
</form>
</div>
);
}