Login and register

This commit is contained in:
2026-02-03 17:00:21 +01:00
parent 56672f30f5
commit 09faa75826
8 changed files with 317 additions and 24 deletions
+5
View File
@@ -0,0 +1,5 @@
import { LoginPage } from "@/components/auth/LoginPage";
export default function Page() {
return <LoginPage />;
}
+5
View File
@@ -0,0 +1,5 @@
import { RegisterPage } from "@/components/auth/RegisterPage";
export default function Page() {
return <RegisterPage />;
}
+8 -22
View File
@@ -1,9 +1,10 @@
"use client";
import Link from "next/link";
import { Radio, Trophy, Zap } from "lucide-react";
import { Radio, Trophy, User, Zap } from "lucide-react";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import { Avatar, AvatarFallback } from "../ui/avatar";
const navs = [
{
@@ -46,7 +47,7 @@ const Navbar = () => {
</Link>
{/* Navigation Links */}
<div className="hidden md:flex items-center gap-1">
<div className="hidden md:flex absolute left-1/2 -translate-x-1/2 items-center gap-1">
{navs.map(({ link, icon: Icon, name }) => (
<Link
key={link}
@@ -64,26 +65,11 @@ const Navbar = () => {
{/* CTA Button */}
<div className="flex items-center gap-4">
<button className="hidden sm:flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm font-semibold transition-all duration-300 hover:scale-105 glow-purple hover:glow-purple-intense">
Start Run
</button>
{/* Mobile menu button */}
<button className="md:hidden p-2 text-muted-foreground hover:text-foreground">
<svg
className="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
<Avatar onClick={() => {}}>
<AvatarFallback>
<User className="m-2" />
</AvatarFallback>
</Avatar>
</div>
</div>
</div>
+95
View File
@@ -0,0 +1,95 @@
"use client";
import Link from "next/link";
import {
ArrowRight,
Github,
KeyRound,
LogIn,
Mail,
UserRound,
Zap,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function LoginPage() {
return (
<div className="min-h-screen flex items-center justify-center relative overflow-hidden">
{/* Background glow */}
<div
className="absolute inset-0 pointer-events-none"
style={{
background:
"radial-gradient(ellipse at top, hsl(270 91% 45% / 0.25) 0%, transparent 60%)",
}}
/>
<Card className="w-full max-w-md relative">
<CardHeader className="text-center">
<div className="mx-auto mb-4 w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
<LogIn className="h-6 w-6 text-primary" />
</div>
<CardTitle className="text-2xl">Welcome back</CardTitle>
<CardDescription>
Log in to continue chasing your personal best.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="[email protected]" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" placeholder="••••••••" />
</div>
</div>
<Button className="w-full" variant="secondary">
Log In
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-border" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-card px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<Button variant="outline">
<Github className="h-4 w-4" />
GitHub
</Button>
<Button variant="outline">
<Mail className="h-4 w-4" />
Google
</Button>
</div>
<p className="text-center text-sm text-muted-foreground">
Don&apos;t have an account?{" "}
<Link href="/register" className="text-primary hover:underline">
Create one
</Link>
</p>
</CardContent>
</Card>
</div>
);
}
+69
View File
@@ -0,0 +1,69 @@
"use client";
import Link from "next/link";
import { ArrowRight, Github, Mail, UserPlus, Zap } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export function RegisterPage() {
return (
<div className="min-h-screen flex items-center justify-center relative overflow-hidden">
{/* Background glow */}
<div
className="absolute inset-0 pointer-events-none"
style={{
background:
"radial-gradient(ellipse at bottom, hsl(270 91% 75% / 0.18) 0%, transparent 60%)",
}}
/>
<Card className="w-full max-w-md relative">
<CardHeader className="text-center">
<div className="mx-auto mb-4 w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center">
<UserPlus className="h-6 w-6 text-primary" />
</div>
<CardTitle className="text-2xl">Create your account</CardTitle>
<CardDescription>
Join the grind. Track runs, set records, compete globally.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="username">Username</Label>
<Input id="username" placeholder="speedrunner67" />
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="[email protected]" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" placeholder="••••••••" />
</div>
</div>
<Button className="w-full" variant="secondary">
Create Account
</Button>
<p className="text-center text-sm text-muted-foreground">
Already have an account?{" "}
<Link href="/login" className="text-primary hover:underline">
Log in
</Link>
</p>
</CardContent>
</Card>
</div>
);
}
+109
View File
@@ -0,0 +1,109 @@
"use client"
import * as React from "react"
import { Avatar as AvatarPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Avatar({
className,
size = "default",
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
size?: "default" | "sm" | "lg"
}) {
return (
<AvatarPrimitive.Root
data-slot="avatar"
data-size={size}
className={cn(
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
className
)}
{...props}
/>
)
}
function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return (
<AvatarPrimitive.Image
data-slot="avatar-image"
className={cn("aspect-square size-full", className)}
{...props}
/>
)
}
function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"
className={cn(
"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
className
)}
{...props}
/>
)
}
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="avatar-badge"
className={cn(
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none",
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
className
)}
{...props}
/>
)
}
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group"
className={cn(
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
className
)}
{...props}
/>
)
}
function AvatarGroupCount({
className,
...props
}: React.ComponentProps<"div">) {
return (
<div
data-slot="avatar-group-count"
className={cn(
"bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
className
)}
{...props}
/>
)
}
export {
Avatar,
AvatarImage,
AvatarFallback,
AvatarBadge,
AvatarGroup,
AvatarGroupCount,
}
+2 -2
View File
@@ -13,9 +13,9 @@ const buttonVariants = cva(
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
"border bg-background shadow-xs hover:bg-accent hover:text-primary dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
"bg-secondary text-secondary-foreground hover:bg-secondary/80 hover:text-primary",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
+24
View File
@@ -0,0 +1,24 @@
"use client"
import * as React from "react"
import { Label as LabelPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props}
/>
)
}
export { Label }