POL-124: Migrate frontend from React Native to Next.js web app

- Replace mobile/ (Expo) with web/ (Next.js 16 + Tailwind + shadcn/ui)
- Pages: login, register, pending, championships, championship detail, registrations, profile, admin
- Logic/view separated: hooks/ for data, components/ for UI, pages compose both
- Types in src/types/ (one interface per file)
- Auth: Zustand store + localStorage tokens + cookie presence flag for proxy
- API layer: axios client with JWT auto-refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dianaka123
2026-02-26 20:35:22 +03:00
parent 390c338b32
commit 9fcd7c1d63
94 changed files with 13713 additions and 10785 deletions

View File

@@ -0,0 +1,7 @@
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-violet-50 to-purple-100 p-4">
<div className="w-full max-w-md">{children}</div>
</div>
);
}

View File

@@ -0,0 +1,49 @@
"use client";
import Link from "next/link";
import { useLoginForm } from "@/hooks/useLoginForm";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
export default function LoginPage() {
const { email, setEmail, password, setPassword, error, isLoading, submit } = useLoginForm();
return (
<Card>
<CardHeader className="text-center">
<div className="mx-auto mb-2 text-4xl">🏆</div>
<CardTitle className="text-2xl">Welcome back</CardTitle>
<CardDescription>Sign in to your account</CardDescription>
</CardHeader>
<form onSubmit={submit}>
<CardContent className="space-y-4">
{error && <p className="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">{error}</p>}
<div className="space-y-1">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} required />
</div>
<div className="space-y-1">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} required />
</div>
</CardContent>
<CardFooter className="flex flex-col gap-3">
<Button type="submit" className="w-full bg-violet-600 hover:bg-violet-700" disabled={isLoading}>
{isLoading ? "Signing in…" : "Sign in"}
</Button>
<p className="text-center text-sm text-gray-500">
No account?{" "}
<Link href="/register" className="font-medium text-violet-600 hover:underline">
Register
</Link>
</p>
</CardFooter>
</form>
</Card>
);
}

View File

@@ -0,0 +1,25 @@
import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
export default function PendingPage() {
return (
<Card className="text-center">
<CardHeader>
<div className="mx-auto mb-2 text-5xl"></div>
<CardTitle className="text-2xl">Awaiting approval</CardTitle>
<CardDescription>
Your organizer account has been submitted. An admin will review it shortly.
</CardDescription>
</CardHeader>
<CardContent>
<p className="mb-6 text-sm text-gray-500">
Once approved you can log in and start creating championships.
</p>
<Button asChild variant="outline" className="w-full">
<Link href="/login">Back to login</Link>
</Button>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,71 @@
"use client";
import Link from "next/link";
import { useRegisterForm } from "@/hooks/useRegisterForm";
import { MemberFields } from "@/components/auth/MemberFields";
import { OrganizerFields } from "@/components/auth/OrganizerFields";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
export default function RegisterPage() {
const { role, setRole, form, update, error, isLoading, submit } = useRegisterForm();
return (
<Card>
<CardHeader className="text-center">
<div className="mx-auto mb-2 text-4xl">🏅</div>
<CardTitle className="text-2xl">Create account</CardTitle>
<CardDescription>Join the pole dance community</CardDescription>
</CardHeader>
<form onSubmit={submit}>
<CardContent className="space-y-4">
{error && <p className="rounded-md bg-red-50 px-3 py-2 text-sm text-red-600">{error}</p>}
<div className="grid grid-cols-2 gap-2">
{(["member", "organizer"] as const).map((r) => (
<button
key={r}
type="button"
onClick={() => setRole(r)}
className={`rounded-lg border-2 p-3 text-sm font-medium transition-colors ${
role === r ? "border-violet-600 bg-violet-50 text-violet-700" : "border-gray-200 text-gray-600 hover:border-gray-300"
}`}
>
{r === "member" ? "🏅 Athlete" : "🏆 Organizer"}
</button>
))}
</div>
<MemberFields
full_name={form.full_name}
email={form.email}
password={form.password}
phone={form.phone}
onChange={update}
/>
{role === "organizer" && (
<OrganizerFields
organization_name={form.organization_name}
instagram_handle={form.instagram_handle}
onChange={update}
/>
)}
</CardContent>
<CardFooter className="flex flex-col gap-3">
<Button type="submit" className="w-full bg-violet-600 hover:bg-violet-700" disabled={isLoading}>
{isLoading ? "Creating…" : role === "member" ? "Create account" : "Submit for approval"}
</Button>
<p className="text-center text-sm text-gray-500">
Have an account?{" "}
<Link href="/login" className="font-medium text-violet-600 hover:underline">
Sign in
</Link>
</p>
</CardFooter>
</form>
</Card>
);
}