feat(04-01): redesign LoginPage with brand presence and OAuth icons
- Change background from bg-background to bg-muted/60 - Add border-t-4 border-t-primary shadow-lg card accent - Add favicon.svg logo above CardTitle - Add auth.loginSubtitle below title - Add Google SVG icon and GitHub SVG icon to OAuth buttons - Add gap-2 to OAuth button className for icon/text spacing - Add auth.loginSubtitle and auth.registerSubtitle to en.json and de.json
This commit is contained in:
@@ -19,7 +19,9 @@
|
|||||||
"displayName": "Anzeigename",
|
"displayName": "Anzeigename",
|
||||||
"noAccount": "Noch kein Konto?",
|
"noAccount": "Noch kein Konto?",
|
||||||
"hasAccount": "Bereits ein Konto?",
|
"hasAccount": "Bereits ein Konto?",
|
||||||
"orContinueWith": "Oder weiter mit"
|
"orContinueWith": "Oder weiter mit",
|
||||||
|
"loginSubtitle": "Melde dich bei deinem Konto an",
|
||||||
|
"registerSubtitle": "Erstelle ein neues Konto"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"title": "Kategorien",
|
"title": "Kategorien",
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
"displayName": "Display Name",
|
"displayName": "Display Name",
|
||||||
"noAccount": "Don't have an account?",
|
"noAccount": "Don't have an account?",
|
||||||
"hasAccount": "Already have an account?",
|
"hasAccount": "Already have an account?",
|
||||||
"orContinueWith": "Or continue with"
|
"orContinueWith": "Or continue with",
|
||||||
|
"loginSubtitle": "Sign in to your account",
|
||||||
|
"registerSubtitle": "Create a new account"
|
||||||
},
|
},
|
||||||
"categories": {
|
"categories": {
|
||||||
"title": "Categories",
|
"title": "Categories",
|
||||||
|
|||||||
118
src/pages/LoginPage.tsx
Normal file
118
src/pages/LoginPage.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { useState } from "react"
|
||||||
|
import { Link, useNavigate } from "react-router-dom"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useAuth } from "@/hooks/useAuth"
|
||||||
|
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"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
|
||||||
|
export default function LoginPage() {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { signIn, signInWithOAuth } = useAuth()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [email, setEmail] = useState("")
|
||||||
|
const [password, setPassword] = useState("")
|
||||||
|
const [error, setError] = useState("")
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
|
||||||
|
async function handleSubmit(e: React.FormEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
setError("")
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
await signIn(email, password)
|
||||||
|
navigate("/")
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : t("common.error"))
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen items-center justify-center bg-muted/60 p-4">
|
||||||
|
<Card className="w-full max-w-sm border-t-4 border-t-primary shadow-lg">
|
||||||
|
<CardHeader className="text-center pb-4">
|
||||||
|
<img src="/favicon.svg" alt="" className="mx-auto mb-3 size-10" aria-hidden="true" />
|
||||||
|
<CardTitle className="text-2xl">{t("app.title")}</CardTitle>
|
||||||
|
<p className="text-sm text-muted-foreground">{t("auth.loginSubtitle")}</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">{t("auth.email")}</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
autoComplete="email"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="password">{t("auth.password")}</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
autoComplete="current-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{error && (
|
||||||
|
<p className="text-sm text-destructive">{error}</p>
|
||||||
|
)}
|
||||||
|
<Button type="submit" className="w-full" disabled={loading}>
|
||||||
|
{t("auth.login")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="relative">
|
||||||
|
<Separator />
|
||||||
|
<span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-card px-2 text-xs text-muted-foreground">
|
||||||
|
{t("auth.orContinueWith")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="flex-1 gap-2"
|
||||||
|
onClick={() => signInWithOAuth("google")}
|
||||||
|
>
|
||||||
|
<svg className="size-4" viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.27-4.74 3.27-8.1z" fill="#4285F4"/>
|
||||||
|
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
|
||||||
|
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
|
||||||
|
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
|
||||||
|
</svg>
|
||||||
|
Google
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="flex-1 gap-2"
|
||||||
|
onClick={() => signInWithOAuth("github")}
|
||||||
|
>
|
||||||
|
<svg className="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||||
|
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z"/>
|
||||||
|
</svg>
|
||||||
|
GitHub
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-4 text-center text-sm text-muted-foreground">
|
||||||
|
{t("auth.noAccount")}{" "}
|
||||||
|
<Link to="/register" className="text-primary underline">
|
||||||
|
{t("auth.register")}
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user