This commit is contained in:
Garret Patti
2026-04-05 17:44:24 -04:00
parent f0666c0649
commit eecee9bc5f
41 changed files with 1405 additions and 28 deletions

158
src/app/login/LoginForm.tsx Normal file
View File

@@ -0,0 +1,158 @@
'use client'
import { useState, useEffect } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
interface Props {
isFirstRun: boolean
}
export default function LoginForm({ isFirstRun }: Props) {
const router = useRouter()
const searchParams = useSearchParams()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const from = searchParams.get('from') ?? '/'
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setError('')
if (isFirstRun && password !== confirmPassword) {
setError('Passwords do not match')
return
}
setLoading(true)
try {
if (isFirstRun) {
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, role: 'admin' }),
})
if (!res.ok) {
const data = await res.json()
setError(data.error ?? 'Registration failed')
return
}
}
const loginRes = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
})
if (!loginRes.ok) {
const data = await loginRes.json()
setError(data.error ?? 'Login failed')
return
}
router.push(from)
router.refresh()
} finally {
setLoading(false)
}
}
return (
<div className="w-full max-w-sm px-6">
<div className="mb-8 text-center">
<span style={{ color: 'var(--accent)', fontSize: '2rem' }}></span>
<h1 className="mt-2 text-2xl font-semibold" style={{ color: 'var(--text-primary)' }}>
MediaLore
</h1>
<p className="text-sm mt-1" style={{ color: 'var(--text-secondary)' }}>
{isFirstRun ? 'Create your admin account to get started' : 'Sign in to your account'}
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1.5" style={{ color: 'var(--text-primary)' }}>
Username
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
autoFocus
autoComplete="username"
className="w-full px-3 py-2 rounded-lg border text-sm"
style={{
backgroundColor: 'var(--surface)',
borderColor: 'var(--border)',
color: 'var(--text-primary)',
}}
/>
</div>
<div>
<label className="block text-sm font-medium mb-1.5" style={{ color: 'var(--text-primary)' }}>
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
autoComplete={isFirstRun ? 'new-password' : 'current-password'}
className="w-full px-3 py-2 rounded-lg border text-sm"
style={{
backgroundColor: 'var(--surface)',
borderColor: 'var(--border)',
color: 'var(--text-primary)',
}}
/>
</div>
{isFirstRun && (
<div>
<label className="block text-sm font-medium mb-1.5" style={{ color: 'var(--text-primary)' }}>
Confirm Password
</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
autoComplete="new-password"
className="w-full px-3 py-2 rounded-lg border text-sm"
style={{
backgroundColor: 'var(--surface)',
borderColor: 'var(--border)',
color: 'var(--text-primary)',
}}
/>
</div>
)}
{error && (
<p className="text-sm" style={{ color: 'var(--error, #ef4444)' }}>
{error}
</p>
)}
<button
type="submit"
disabled={loading}
className="w-full py-2 px-4 rounded-lg text-sm font-medium transition-opacity"
style={{
backgroundColor: 'var(--accent)',
color: 'var(--background)',
opacity: loading ? 0.6 : 1,
}}
>
{loading ? 'Please wait…' : isFirstRun ? 'Create Account' : 'Sign In'}
</button>
</form>
</div>
)
}