Add theme system with color presets, fix login page styling, persist auth session
Theme system with 5 color presets (Slate, Emerald, Violet, Amber, Rose) and light/dark/system mode. User menu in sidebar with theme picker and sign out. Login page uses standalone dark branded styling with autofill override. Auth persists in sessionStorage across refreshes.
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
import { useState } from 'react'
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
||||
import { createFileRoute, useRouter, redirect } from '@tanstack/react-router'
|
||||
import { useAuthStore } from '@/stores/auth.store'
|
||||
import { login } from '@/api/auth'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
|
||||
export const Route = createFileRoute('/login')({
|
||||
beforeLoad: () => {
|
||||
const { token } = useAuthStore.getState()
|
||||
if (token) {
|
||||
throw redirect({ to: '/accounts' })
|
||||
}
|
||||
},
|
||||
component: LoginPage,
|
||||
})
|
||||
|
||||
function LoginPage() {
|
||||
const navigate = useNavigate()
|
||||
const router = useRouter()
|
||||
const setAuth = useAuthStore((s) => s.setAuth)
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
@@ -27,7 +29,8 @@ function LoginPage() {
|
||||
try {
|
||||
const res = await login(email, password)
|
||||
setAuth(res.token, res.user)
|
||||
navigate({ to: '/accounts' })
|
||||
await router.invalidate()
|
||||
await router.navigate({ to: '/accounts', replace: true })
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Login failed')
|
||||
} finally {
|
||||
@@ -36,44 +39,55 @@ function LoginPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-background">
|
||||
<Card className="w-full max-w-sm">
|
||||
<CardHeader className="text-center">
|
||||
<CardTitle className="text-2xl">Forte</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">Sign in to your account</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<p className="text-sm text-destructive">{error}</p>
|
||||
)}
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? 'Signing in...' : 'Sign in'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div
|
||||
className="flex min-h-screen items-center justify-center"
|
||||
style={{ background: 'linear-gradient(135deg, #0f1724 0%, #142038 100%)' }}
|
||||
>
|
||||
<div
|
||||
className="w-full max-w-sm rounded-xl border p-8 shadow-2xl"
|
||||
style={{ backgroundColor: '#131c2e', borderColor: '#1e2d45' }}
|
||||
>
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold" style={{ color: '#d8dfe9' }}>Forte</h1>
|
||||
<p className="text-sm mt-1" style={{ color: '#6b7a8d' }}>Music Store Management</p>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium" style={{ color: '#b0bec5' }}>Email</label>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="h-9 w-full rounded-md border px-3 py-1 text-sm outline-none login-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium" style={{ color: '#b0bec5' }}>Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="h-9 w-full rounded-md border px-3 py-1 text-sm outline-none login-input"
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<p className="text-sm" style={{ color: '#e57373' }}>{error}</p>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="h-9 w-full rounded-md border text-sm font-medium transition-colors disabled:opacity-50"
|
||||
style={{ backgroundColor: 'transparent', color: '#d0d8e0', borderColor: '#3a4a62' }}
|
||||
onMouseEnter={(e) => { (e.target as HTMLElement).style.backgroundColor = '#1e2d45' }}
|
||||
onMouseLeave={(e) => { (e.target as HTMLElement).style.backgroundColor = 'transparent' }}
|
||||
>
|
||||
{loading ? 'Signing in...' : 'Sign in'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user