Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 109 additions & 103 deletions apps/cwru-wtf/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,181 +1,187 @@
'use client';
"use client";

import { type FormEvent, useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { signIn } from 'next-auth/react';
import { ArrowLeft, LoaderCircle, Lock, ShieldCheck, Sparkles } from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { type FormEvent, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { signIn } from "next-auth/react";
import { ArrowLeft, LoaderCircle, Lock } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import Wordmark from "@/components/wordmark";

export default function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [authError, setAuthError] = useState("");
const router = useRouter();

const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
setIsLoading(true);
setAuthError("");

try {
const result = await signIn('credentials', {
const result = await signIn("credentials", {
email,
password,
redirect: false,
});

if (result?.error) {
toast.error('Invalid credentials. Please try again.');
setAuthError("Those credentials did not match an admin account.");
toast.error("Invalid credentials. Please try again.");
} else {
toast.success('Logged in successfully.');
router.push('/admin');
toast.success("Logged in successfully.");
router.push("/admin");
router.refresh();
}
} catch (error) {
console.error('Login error:', error);
toast.error('Something went wrong. Please try again.');
console.error("Login error:", error);
setAuthError("We could not sign you in right now. Please try again.");
toast.error("Something went wrong. Please try again.");
} finally {
setIsLoading(false);
}
};

return (
<div className="min-h-screen overflow-hidden bg-[#07131f] text-slate-50">
<div className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.24),_transparent_24%),radial-gradient(circle_at_bottom_right,_rgba(45,212,191,0.18),_transparent_32%),linear-gradient(180deg,_#07131f_0%,_#020617_100%)]" />
<div className="absolute left-[-6rem] top-16 h-72 w-72 rounded-full bg-cyan-400/12 blur-3xl" />
<div className="absolute bottom-[-8rem] right-[-3rem] h-80 w-80 rounded-full bg-teal-300/12 blur-3xl" />
<div className="absolute inset-0 opacity-30 [background-image:linear-gradient(to_right,rgba(148,163,184,0.08)_1px,transparent_1px),linear-gradient(to_bottom,rgba(148,163,184,0.08)_1px,transparent_1px)] [background-size:72px_72px] [mask-image:radial-gradient(circle_at_center,black,transparent_82%)]" />
</div>

<main className="relative mx-auto flex min-h-screen max-w-6xl items-center px-4 py-8 sm:px-6 lg:px-8">
<div className="grid w-full gap-6 lg:grid-cols-[1.08fr_0.92fr]">
<section className="rounded-[32px] border border-white/10 bg-white/5 p-6 shadow-[0_40px_140px_-48px_rgba(8,47,73,0.95)] backdrop-blur-xl sm:p-8 lg:p-10">
<Link
href="/"
className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-4 py-2 text-sm text-slate-200 transition hover:border-cyan-300/30 hover:bg-white/10 hover:text-white"
<div className="min-h-[100svh] bg-background text-foreground">
<a
href="#login-main"
className="focus-ring sr-only z-50 rounded-lg bg-primary px-4 py-3 text-primary-foreground focus:fixed focus:left-4 focus:top-4 focus:not-sr-only"
>
Skip to content
</a>
<div className="mx-auto flex min-h-[100svh] w-full max-w-[1160px] flex-col px-6">
<header className="screen-line-after flex min-h-20 items-center justify-between gap-4 py-4">
<Link
href="/"
className="focus-ring inline-flex min-h-11 items-center gap-3 rounded-lg text-foreground"
>
<ArrowLeft
aria-hidden="true"
className="h-4 w-4 text-muted-foreground"
/>
<span className="font-brand text-lg font-semibold">
<Wordmark />
</span>
</Link>
<span className="rounded-md bg-muted px-2 py-1 font-mono text-xs uppercase tracking-[0.16em] text-muted-foreground">
Admin access
</span>
</header>

<main
id="login-main"
className="flex flex-1 items-center justify-center py-14 md:py-20"
>
<section
aria-labelledby="sign-in-heading"
className="w-full max-w-md rounded-xl border border-border bg-card p-6 sm:p-8"
>
<h1
id="sign-in-heading"
className="font-brand text-2xl font-semibold tracking-[-0.02em] text-card-foreground"
>
<ArrowLeft className="h-4 w-4" />
Back to home
</Link>

<div className="mt-8 inline-flex items-center gap-2 rounded-full border border-cyan-300/20 bg-cyan-300/10 px-3 py-1 text-xs font-medium uppercase tracking-[0.28em] text-cyan-100/90">
<Sparkles className="h-3.5 w-3.5" />
Private Admin Portal
</div>

<h1 className="mt-5 max-w-2xl font-brand text-4xl leading-tight text-white sm:text-5xl">
Minimal, premium access for the team behind CWRU.WTF.
Sign in to admin
</h1>

<p className="mt-4 max-w-xl text-sm leading-7 text-slate-300 sm:text-base">
Reviewer access now matches the new admin dashboard with calmer
contrast, a tighter layout, and a smoother handoff into the
moderation queue.
<p className="mt-2 text-body-sm text-muted-foreground">
Enter your admin credentials to continue.
</p>

<div className="mt-8 grid gap-4 sm:grid-cols-2">
<div className="rounded-[26px] border border-white/10 bg-slate-950/55 p-5">
<p className="text-xs uppercase tracking-[0.22em] text-slate-500">
What&apos;s inside
</p>
<p className="mt-3 text-lg font-semibold text-white">
Submission review
</p>
<p className="mt-2 text-sm leading-6 text-slate-300">
Cleaner filtering, quick contact details, and a polished
moderation surface.
</p>
</div>

<div className="rounded-[26px] border border-white/10 bg-slate-950/55 p-5">
<div className="flex h-11 w-11 items-center justify-center rounded-2xl bg-cyan-400/12 text-cyan-100">
<ShieldCheck className="h-5 w-5" />
</div>
<p className="mt-4 text-lg font-semibold text-white">
Restricted access
</p>
<p className="mt-2 text-sm leading-6 text-slate-300">
Only approved admins can enter and manage community
applications.
<form
onSubmit={handleSubmit}
className="mt-8 space-y-5"
aria-describedby={authError ? "sign-in-error" : undefined}
>
{authError ? (
<p
id="sign-in-error"
role="alert"
className="rounded-xl border border-destructive/20 bg-destructive/10 px-4 py-3 text-body-sm text-destructive"
>
{authError}
</p>
</div>
</div>
</section>

<section className="rounded-[32px] border border-white/10 bg-slate-950/70 p-6 backdrop-blur-xl sm:p-8 lg:p-10">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-cyan-400/12 text-cyan-100">
<Lock className="h-5 w-5" />
</div>
) : null}

<h2 className="mt-6 text-3xl font-semibold text-white">
Sign in to admin
</h2>
<p className="mt-2 text-sm leading-6 text-slate-300">
Use your admin credentials to open the submission console.
</p>

<form onSubmit={handleSubmit} className="mt-8 space-y-4">
<div>
<label
htmlFor="email"
className="mb-2 block text-sm font-medium text-slate-200"
className="mb-2 block font-mono text-caption uppercase tracking-[0.14em] text-muted-foreground"
>
Email
</label>
<input
id="email"
name="email"
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
onChange={(event) => {
setEmail(event.target.value);
setAuthError("");
}}
autoComplete="email"
autoCapitalize="none"
spellCheck={false}
required
placeholder="admin@cwru.wtf"
className="w-full rounded-[22px] border border-white/10 bg-white/5 px-4 py-3 text-white outline-none transition placeholder:text-slate-500 focus:border-cyan-300/40 focus:bg-white/8 focus:ring-2 focus:ring-cyan-300/20"
className="focus-ring h-12 w-full rounded-xl border border-border bg-background px-4 text-body-sm text-foreground placeholder:text-muted-foreground focus:border-foreground"
/>
</div>

<div>
<label
htmlFor="password"
className="mb-2 block text-sm font-medium text-slate-200"
className="mb-2 block font-mono text-caption uppercase tracking-[0.14em] text-muted-foreground"
>
Password
</label>
<input
id="password"
name="password"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
onChange={(event) => {
setPassword(event.target.value);
setAuthError("");
}}
autoComplete="current-password"
required
placeholder="••••••••"
className="w-full rounded-[22px] border border-white/10 bg-white/5 px-4 py-3 text-white outline-none transition placeholder:text-slate-500 focus:border-cyan-300/40 focus:bg-white/8 focus:ring-2 focus:ring-cyan-300/20"
className="focus-ring h-12 w-full rounded-xl border border-border bg-background px-4 text-body-sm text-foreground placeholder:text-muted-foreground focus:border-foreground"
/>
</div>

<Button
type="submit"
disabled={isLoading}
className="h-12 w-full rounded-[22px] bg-cyan-300 text-slate-950 hover:bg-cyan-200 disabled:cursor-not-allowed disabled:opacity-60"
aria-busy={isLoading}
className="h-12 w-full rounded-xl bg-primary text-base text-primary-foreground transition-[transform,background-color] hover:bg-primary/90 active:scale-[0.98] disabled:cursor-not-allowed"
>
{isLoading ? (
<LoaderCircle className="h-4 w-4 animate-spin" />
<LoaderCircle
aria-hidden="true"
className="h-4 w-4 animate-spin"
/>
) : (
<Lock className="h-4 w-4" />
<Lock aria-hidden="true" className="h-4 w-4" />
)}
{isLoading ? 'Signing in...' : 'Enter admin dashboard'}
{isLoading ? "Signing in…" : "Enter admin dashboard"}
</Button>
</form>

<p className="mt-6 text-sm leading-6 text-slate-400">
Need an admin account? Contact the current site operator to be
added to the reviewer list.
</p>
</section>
</div>
</main>
</main>

<footer className="screen-line-before flex flex-col items-center gap-2 py-8 text-center sm:flex-row sm:justify-between sm:text-left">
<span className="font-brand text-lg font-semibold text-foreground">
<Wordmark />
</span>
<span className="font-mono text-caption text-muted-foreground">
We Tinker Fearlessly
</span>
</footer>
</div>
</div>
);
}
Loading