Restructure into full project layout

This commit is contained in:
2026-06-16 23:06:16 -06:00
parent de4862b2d1
commit 57765496a6
74 changed files with 4441 additions and 3 deletions
+228
View File
@@ -0,0 +1,228 @@
import React, { useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore'
import { Button, Input, ErrorMessage } from '@/components/ui'
import { AxiosError } from 'axios'
// ─── Shared card wrapper ──────────────────────────────────────────────────────
function AuthCard({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-bg-base flex items-center justify-center p-4">
{/* Subtle background texture */}
<div
className="absolute inset-0 opacity-5"
style={{
backgroundImage:
'radial-gradient(circle at 25% 25%, #7C3AED 0%, transparent 50%), radial-gradient(circle at 75% 75%, #4C1D95 0%, transparent 50%)',
}}
/>
<div className="relative w-full max-w-md">
{/* Logo */}
<div className="flex flex-col items-center mb-8 gap-2">
<svg viewBox="0 0 60 60" className="w-14 h-14" fill="none">
<polygon
points="30,3 57,21 46.5,51 13.5,51 3,21"
stroke="#7C3AED"
strokeWidth="2"
fill="none"
/>
<polygon
points="30,12 48,24 41,44 19,44 12,24"
stroke="#9F67FF"
strokeWidth="1"
fill="rgba(124,58,237,0.1)"
/>
<circle cx="30" cy="28" r="5" fill="#7C3AED" opacity="0.8" />
</svg>
<h1 className="font-display text-2xl text-text-primary tracking-wide">Commander Forge</h1>
<p className="text-text-muted text-sm">AI-powered deck building for Commander</p>
</div>
<div className="bg-bg-surface border border-bg-border rounded-xl p-8 shadow-card">
{children}
</div>
</div>
</div>
)
}
function getErrorMessage(err: unknown): string {
if (err instanceof AxiosError) {
const detail = err.response?.data?.detail
if (typeof detail === 'string') return detail
if (Array.isArray(detail)) return detail.map((d) => d.msg).join('. ')
}
return 'Something went wrong. Please try again.'
}
// ─── Login ────────────────────────────────────────────────────────────────────
export function LoginPage() {
const navigate = useNavigate()
const { login, loading } = useAuthStore()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
try {
await login(email, password)
navigate('/decks')
} catch (err) {
setError(getErrorMessage(err))
}
}
return (
<AuthCard>
<h2 className="font-display text-lg text-text-primary mb-6">Sign in</h2>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
{error && <ErrorMessage message={error} />}
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
autoComplete="email"
required
/>
<Input
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
autoComplete="current-password"
required
/>
<Button type="submit" loading={loading} className="w-full mt-2" size="lg">
Sign in
</Button>
</form>
<p className="text-center text-sm text-text-muted mt-6">
No account?{' '}
<Link to="/register" className="text-accent-violet-light hover:underline">
Request access
</Link>
</p>
</AuthCard>
)
}
// ─── Register ─────────────────────────────────────────────────────────────────
export function RegisterPage() {
const navigate = useNavigate()
const { register, loading } = useAuthStore()
const [email, setEmail] = useState('')
const [displayName, setDisplayName] = useState('')
const [password, setPassword] = useState('')
const [confirm, setConfirm] = useState('')
const [error, setError] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
if (password !== confirm) {
setError('Passwords do not match.')
return
}
try {
await register(email, password, displayName || undefined)
navigate('/pending')
} catch (err) {
setError(getErrorMessage(err))
}
}
return (
<AuthCard>
<h2 className="font-display text-lg text-text-primary mb-1">Request access</h2>
<p className="text-text-muted text-sm mb-6">
Accounts require admin approval before you can start building.
</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
{error && <ErrorMessage message={error} />}
<Input
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
autoComplete="email"
required
/>
<Input
label="Display name (optional)"
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
placeholder="Your name"
/>
<Input
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="At least 8 characters"
autoComplete="new-password"
required
/>
<Input
label="Confirm password"
type="password"
value={confirm}
onChange={(e) => setConfirm(e.target.value)}
placeholder="••••••••"
autoComplete="new-password"
required
/>
<Button type="submit" loading={loading} className="w-full mt-2" size="lg">
Request access
</Button>
</form>
<p className="text-center text-sm text-text-muted mt-6">
Already have an account?{' '}
<Link to="/login" className="text-accent-violet-light hover:underline">
Sign in
</Link>
</p>
</AuthCard>
)
}
// ─── Pending ──────────────────────────────────────────────────────────────────
export function PendingPage() {
const { logout } = useAuthStore()
const navigate = useNavigate()
return (
<AuthCard>
<div className="text-center flex flex-col items-center gap-4">
<div className="w-16 h-16 rounded-full bg-accent-violet/20 border border-accent-violet/40 flex items-center justify-center">
<svg className="w-8 h-8 text-accent-violet-light" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M12 6v6l4 2M12 2a10 10 0 100 20 10 10 0 000-20z" />
</svg>
</div>
<div>
<h2 className="font-display text-lg text-text-primary mb-2">Access pending</h2>
<p className="text-text-secondary text-sm leading-relaxed">
Your account is awaiting admin approval. You'll be able to sign in once approved.
</p>
</div>
<button
onClick={() => { logout(); navigate('/login') }}
className="text-sm text-text-muted hover:text-text-secondary transition-colors"
>
Back to sign in
</button>
</div>
</AuthCard>
)
}