zu fabricjs gewechselt, noch nicht funktionsfähig
This commit is contained in:
@@ -1,16 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Location, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Sparkles, ShieldCheck, Images, ArrowRight, Loader2 } from 'lucide-react';
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import AppearanceToggleDropdown from '@/components/appearance-dropdown';
|
||||
import AppLogoIcon from '@/components/app-logo-icon';
|
||||
|
||||
import { useAuth } from '../auth/context';
|
||||
import { ADMIN_HOME_PATH } from '../constants';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface LocationState {
|
||||
from?: Location;
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
const featureIcons = [Sparkles, ShieldCheck, Images];
|
||||
|
||||
export default function LoginPage(): JSX.Element {
|
||||
const { status, login } = useAuth();
|
||||
const { t } = useTranslation('auth');
|
||||
const location = useLocation();
|
||||
@@ -35,26 +42,120 @@ export default function LoginPage() {
|
||||
return ADMIN_HOME_PATH;
|
||||
}, [location.state]);
|
||||
|
||||
const featureList = React.useMemo(() => {
|
||||
const raw = t('login.features', { returnObjects: true }) as unknown;
|
||||
if (!Array.isArray(raw)) {
|
||||
return [] as Array<{ text: string; Icon: typeof Sparkles }>;
|
||||
}
|
||||
return (raw as string[]).map((entry, index) => ({
|
||||
text: entry,
|
||||
Icon: featureIcons[index % featureIcons.length],
|
||||
}));
|
||||
}, [t]);
|
||||
|
||||
const isLoading = status === 'loading';
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex min-h-screen max-w-sm flex-col justify-center p-6">
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="text-lg font-semibold">{t('login.title')}</h1>
|
||||
<AppearanceToggleDropdown />
|
||||
</div>
|
||||
<div className="space-y-4 text-sm text-muted-foreground">
|
||||
<p>{t('login.lead')}</p>
|
||||
{oauthError && (
|
||||
<div className="rounded border border-red-300 bg-red-50 p-2 text-sm text-red-700">
|
||||
{t('login.oauth_error', { message: oauthError })}
|
||||
<div className="relative min-h-screen overflow-hidden bg-[var(--brand-navy)] text-white">
|
||||
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,var(--brand-rose-soft)_0%,rgba(3,7,18,0.65)_55%,rgba(15,76,117,0.9)_100%)] opacity-95" />
|
||||
<div className="pointer-events-none absolute inset-y-0 right-[-25%] w-[55%] bg-[radial-gradient(circle_at_center,var(--brand-sky)_0%,rgba(255,255,255,0)_70%)] opacity-40" />
|
||||
<div className="pointer-events-none absolute inset-y-0 left-[-20%] w-[45%] bg-[radial-gradient(circle_at_center,var(--brand-rose)_0%,rgba(255,255,255,0)_65%)] opacity-35" />
|
||||
|
||||
<div className="relative z-10 flex min-h-screen flex-col">
|
||||
<header className="mx-auto flex w-full max-w-6xl items-center justify-between px-6 pt-10">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-white/15 shadow-lg shadow-black/10 backdrop-blur">
|
||||
<AppLogoIcon className="h-7 w-7 text-white" />
|
||||
</span>
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.35em] text-white/70">{t('login.badge')}</p>
|
||||
<p className="text-lg font-semibold">Fotospiel</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
className="w-full"
|
||||
disabled={status === 'loading'}
|
||||
onClick={() => login(redirectTarget)}
|
||||
>
|
||||
{status === 'loading' ? t('login.loading') : t('login.cta')}
|
||||
</Button>
|
||||
<AppearanceToggleDropdown />
|
||||
</header>
|
||||
|
||||
<main className="mx-auto flex w-full max-w-6xl flex-1 flex-col px-6 pb-16 pt-12">
|
||||
<div className="grid flex-1 gap-12 lg:grid-cols-[0.95fr_1.05fr]" data-testid="tenant-login-layout">
|
||||
<section className="order-2 space-y-10 lg:order-1">
|
||||
<div className="space-y-5">
|
||||
<span className="inline-flex items-center gap-2 rounded-full border border-white/20 bg-white/10 px-4 py-1 text-sm font-medium text-white/80 backdrop-blur">
|
||||
<Sparkles className="h-4 w-4 text-[var(--brand-gold)]" />
|
||||
{t('login.badge')}
|
||||
</span>
|
||||
<h1 className="text-4xl font-semibold leading-tight sm:text-5xl">
|
||||
{t('login.hero_title')}
|
||||
</h1>
|
||||
<p className="max-w-xl text-base text-white/80 sm:text-lg">
|
||||
{t('login.hero_subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{featureList.length ? (
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{featureList.map(({ text, Icon }, index) => (
|
||||
<div
|
||||
key={`login-feature-${index}`}
|
||||
className="group relative overflow-hidden rounded-2xl border border-white/15 bg-white/10 p-5 shadow-lg shadow-black/5 backdrop-blur transition hover:border-white/35"
|
||||
>
|
||||
<Icon className="mb-3 h-5 w-5 text-[var(--brand-gold)] transition group-hover:text-white" />
|
||||
<p className="text-sm text-white/90">{text}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<p className="flex items-center gap-2 text-sm text-white/70">
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
{t('login.lead')}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="order-1 lg:order-2">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 -translate-y-4 translate-x-6 scale-95 rounded-3xl bg-white/20 opacity-50 blur-2xl" />
|
||||
<div className="relative overflow-hidden rounded-3xl border border-white/20 bg-white/90 p-10 text-slate-900 shadow-2xl shadow-black/20 backdrop-blur-xl dark:border-white/10 dark:bg-slate-900/90 dark:text-slate-50">
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-semibold">{t('login.title')}</h2>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300">{t('login.panel_copy')}</p>
|
||||
</div>
|
||||
|
||||
{oauthError ? (
|
||||
<Alert className="border-red-400/40 bg-red-50/80 text-red-800 dark:border-red-500/40 dark:bg-red-500/10 dark:text-red-100">
|
||||
<AlertTitle>{t('login.oauth_error_title')}</AlertTitle>
|
||||
<AlertDescription>{t('login.oauth_error', { message: oauthError })}</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
className="group flex w-full items-center justify-center gap-2 rounded-full bg-gradient-to-r from-[var(--brand-rose)] via-[var(--brand-gold)] to-[var(--brand-sky)] px-8 py-3 text-base font-semibold text-slate-900 shadow-lg shadow-rose-400/30 transition hover:opacity-90 focus-visible:ring-4 focus-visible:ring-brand-rose/40 dark:text-slate-900"
|
||||
disabled={isLoading}
|
||||
onClick={() => login(redirectTarget)}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 className="h-5 w-5 animate-spin" />
|
||||
{t('login.loading')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t('login.cta')}
|
||||
<ArrowRight className="h-5 w-5 transition group-hover:translate-x-1" />
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<p className="text-xs leading-relaxed text-slate-500 dark:text-slate-300">
|
||||
{t('login.support')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user