385 lines
20 KiB
TypeScript
385 lines
20 KiB
TypeScript
/**
|
|
* ANNOTATED COPY — every line explained
|
|
* Source: src/app/login/page.tsx
|
|
* NOT used by the app — read this to learn how the real file works
|
|
*/
|
|
// Next.js: this file runs in the BROWSER (needs useState, onClick, localStorage, etc.)
|
|
'use client';
|
|
// (blank line — separates logical blocks for readability)
|
|
// Import React — core UI library (components, hooks, JSX)
|
|
import { useState } from 'react';
|
|
// Next.js Link — fast client-side navigation without full page reload
|
|
import Link from 'next/link';
|
|
// Next.js App Router hooks — useRouter, useParams, useSearchParams
|
|
import { useRouter } from 'next/navigation';
|
|
// Import project module (@/ alias = src/ folder in tsconfig)
|
|
import Button from '@/components/ui/Button';
|
|
// Import project module (@/ alias = src/ folder in tsconfig)
|
|
import { useAuthStore } from '@/store/auth';
|
|
// Import project module (@/ alias = src/ folder in tsconfig)
|
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
// Import project module (@/ alias = src/ folder in tsconfig)
|
|
import { DEMO_EMAIL } from '@/lib/constants';
|
|
// (blank line — separates logical blocks for readability)
|
|
// Default export — main component/page Next.js or other files import
|
|
export default function LoginPage() {
|
|
// Next.js router — programmatic navigation (router.push)
|
|
const router = useRouter();
|
|
// Zustand selector — subscribe to slice of global store
|
|
const { login, register } = useAuthStore();
|
|
// Custom hook — returns t() translator and current locale
|
|
const { t } = useTranslation();
|
|
// React useState — local component state that triggers re-render on change
|
|
const [isRegister, setIsRegister] = useState(false);
|
|
// React useState — local component state that triggers re-render on change
|
|
const [error, setError] = useState('');
|
|
// React useState — local component state that triggers re-render on change
|
|
const [form, setForm] = useState({
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
name: '',
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
email: '',
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
password: '',
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
phone: '',
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
street: '',
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
city: '',
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
state: '',
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
zip: '',
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
});
|
|
// (blank line — separates logical blocks for readability)
|
|
// Line 28: const handleSubmit = (e: React.FormEvent) => {
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
// Line 29: e.preventDefault();
|
|
e.preventDefault();
|
|
// Line 30: setError('');
|
|
setError('');
|
|
// (blank line — separates logical blocks for readability)
|
|
// Conditional branch — different behavior based on runtime value
|
|
if (isRegister) {
|
|
// Line 33: const success = register({
|
|
const success = register({
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
name: form.name,
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
email: form.email,
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
password: form.password,
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
phone: form.phone,
|
|
// Line 38: address: {
|
|
address: {
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
street: form.street,
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
city: form.city,
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
state: form.state,
|
|
// Property or array item — trailing comma allowed in TypeScript
|
|
zip: form.zip,
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
},
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
});
|
|
// Conditional branch — different behavior based on runtime value
|
|
if (success) router.push('/account');
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
} else {
|
|
// Line 47: const success = login(form.email, form.password);
|
|
const success = login(form.email, form.password);
|
|
// Conditional branch — different behavior based on runtime value
|
|
if (success) {
|
|
// Line 49: router.push('/account');
|
|
router.push('/account');
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
} else {
|
|
// Line 51: setError(t('auth.invalidCredentials', { email: DEMO_EMAIL...
|
|
setError(t('auth.invalidCredentials', { email: DEMO_EMAIL }));
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
}
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
}
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
};
|
|
// (blank line — separates logical blocks for readability)
|
|
// Return JSX — describes UI tree React renders to the DOM
|
|
return (
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div className="flex min-h-[70vh] items-center justify-center bg-gray-50 px-4 py-12">
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div className="w-full max-w-md">
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div className="mb-8 text-center">
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div className="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-brand-700">
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<span className="font-display text-xl font-bold text-gold-400">
|
|
// Line 62: {t('site.initials')}
|
|
{t('site.initials')}
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</span>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<h1 className="font-display text-2xl font-bold text-brand-900">
|
|
// Line 66: {isRegister ? t('auth.createAccount') : t('auth.welcomeBa...
|
|
{isRegister ? t('auth.createAccount') : t('auth.welcomeBack')}
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</h1>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<p className="mt-1 text-sm text-gray-500">
|
|
// Line 69: {isRegister
|
|
{isRegister
|
|
// Line 70: ? t('auth.joinTagline', { name: t('site.name') })
|
|
? t('auth.joinTagline', { name: t('site.name') })
|
|
// Line 71: : t('auth.signInTagline', { name: t('site.name') })}
|
|
: t('auth.signInTagline', { name: t('site.name') })}
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</p>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// (blank line — separates logical blocks for readability)
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div className="card-premium p-8">
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
// Line 77: {isRegister && (
|
|
{isRegister && (
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<label className="label-text">{t('auth.fullName')}</label>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<input
|
|
// Line 81: required
|
|
required
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="input-field"
|
|
// Line 83: value={form.name}
|
|
value={form.name}
|
|
// Change handler — runs when input/select value changes
|
|
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
|
// Line 85: />
|
|
/>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// Line 87: )}
|
|
)}
|
|
// (blank line — separates logical blocks for readability)
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<label className="label-text">{t('auth.email')}</label>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<input
|
|
// Line 92: required
|
|
required
|
|
// Line 93: type="email"
|
|
type="email"
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="input-field"
|
|
// Line 95: value={form.email}
|
|
value={form.email}
|
|
// Change handler — runs when input/select value changes
|
|
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
|
// Line 97: />
|
|
/>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// (blank line — separates logical blocks for readability)
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<label className="label-text">{t('auth.password')}</label>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<input
|
|
// Line 103: required
|
|
required
|
|
// Line 104: type="password"
|
|
type="password"
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="input-field"
|
|
// Line 106: value={form.password}
|
|
value={form.password}
|
|
// Change handler — runs when input/select value changes
|
|
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
|
// Line 108: />
|
|
/>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// (blank line — separates logical blocks for readability)
|
|
// Line 111: {isRegister && (
|
|
{isRegister && (
|
|
// React Fragment — group elements without extra wrapper DOM node
|
|
<>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<label className="label-text">{t('auth.phone')}</label>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<input
|
|
// Line 116: required
|
|
required
|
|
// Line 117: type="tel"
|
|
type="tel"
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="input-field"
|
|
// Line 119: value={form.phone}
|
|
value={form.phone}
|
|
// Change handler — runs when input/select value changes
|
|
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
|
// Line 121: />
|
|
/>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<label className="label-text">{t('auth.street')}</label>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<input
|
|
// Line 126: required
|
|
required
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="input-field"
|
|
// Line 128: value={form.street}
|
|
value={form.street}
|
|
// Change handler — runs when input/select value changes
|
|
onChange={(e) => setForm({ ...form, street: e.target.value })}
|
|
// Line 130: />
|
|
/>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div className="grid grid-cols-2 gap-3">
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<label className="label-text">{t('auth.city')}</label>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<input
|
|
// Line 136: required
|
|
required
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="input-field"
|
|
// Line 138: value={form.city}
|
|
value={form.city}
|
|
// Change handler — runs when input/select value changes
|
|
onChange={(e) => setForm({ ...form, city: e.target.value })}
|
|
// Line 140: />
|
|
/>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<label className="label-text">{t('auth.state')}</label>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<input
|
|
// Line 145: required
|
|
required
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="input-field"
|
|
// Line 147: value={form.state}
|
|
value={form.state}
|
|
// Change handler — runs when input/select value changes
|
|
onChange={(e) => setForm({ ...form, state: e.target.value })}
|
|
// Line 149: />
|
|
/>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<label className="label-text">{t('auth.zip')}</label>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<input
|
|
// Line 155: required
|
|
required
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="input-field"
|
|
// Line 157: value={form.zip}
|
|
value={form.zip}
|
|
// Change handler — runs when input/select value changes
|
|
onChange={(e) => setForm({ ...form, zip: e.target.value })}
|
|
// Line 159: />
|
|
/>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</>
|
|
// Line 162: )}
|
|
)}
|
|
// (blank line — separates logical blocks for readability)
|
|
// Line 164: {error && (
|
|
{error && (
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<p className="rounded-lg bg-red-50 px-4 py-2 text-sm text-red-600">{error}</p>
|
|
// Line 166: )}
|
|
)}
|
|
// (blank line — separates logical blocks for readability)
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<Button type="submit" variant="primary" size="lg" className="w-full">
|
|
// Line 169: {isRegister ? t('auth.register') : t('auth.signIn')}
|
|
{isRegister ? t('auth.register') : t('auth.signIn')}
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</Button>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</form>
|
|
// (blank line — separates logical blocks for readability)
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div className="mt-6 text-center">
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<button
|
|
// Click handler — runs when user clicks (must be client component)
|
|
onClick={() => {
|
|
// Line 176: setIsRegister(!isRegister);
|
|
setIsRegister(!isRegister);
|
|
// Line 177: setError('');
|
|
setError('');
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
}}
|
|
// Tailwind CSS utility classes — styling (colors, spacing, layout)
|
|
className="text-sm font-medium text-brand-600 hover:text-brand-800"
|
|
// Line 180: >
|
|
>
|
|
// Line 181: {isRegister ? t('auth.hasAccount') : t('auth.noAccount')}
|
|
{isRegister ? t('auth.hasAccount') : t('auth.noAccount')}
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</button>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// (blank line — separates logical blocks for readability)
|
|
// Line 185: {!isRegister && (
|
|
{!isRegister && (
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<div className="mt-4 rounded-lg bg-brand-50 px-4 py-3 text-center">
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
<p className="text-xs text-brand-700">
|
|
// Line 188: {t('auth.demo', { email: DEMO_EMAIL })}
|
|
{t('auth.demo', { email: DEMO_EMAIL })}
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</p>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// Line 191: )}
|
|
)}
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// JSX element — HTML-like tag becomes React component in browser
|
|
</div>
|
|
// Line 195: );
|
|
);
|
|
// Closing brace — end of block (function, if, object, JSX)
|
|
}
|