Skip to content

Commit 98ef626

Browse files
committed
DRY and improve sign in/up design
1 parent 6e7be81 commit 98ef626

5 files changed

Lines changed: 325 additions & 220 deletions

File tree

common/messages/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,7 @@
13261326
"signin.seo.title": "Anmelden",
13271327
"signin.submit": "Anmelden",
13281328
"signin.title": "Anmelden",
1329+
"signin.subtitle": "Willkommen zurück — mach da weiter, wo du aufgehört hast.",
13291330
"signup.creating_profile": "Profil wird erstellt...",
13301331
"signup.email": "E-Mail",
13311332
"signup.email_placeholder": "sie@beispiel.de",

common/messages/fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,7 @@
13251325
"signin.seo.title": "Se connecter",
13261326
"signin.submit": "Se connecter",
13271327
"signin.title": "Se connecter",
1328+
"signin.subtitle": "Bon retour — reprends là où tu t'étais arrêté.",
13281329
"signup.creating_profile": "Création de votre profil...",
13291330
"signup.email": "E-mail",
13301331
"signup.email_placeholder": "vous@exemple.com",

web/components/auth/auth-form.tsx

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import clsx from 'clsx'
2+
import {InputHTMLAttributes, ReactNode} from 'react'
3+
4+
// Shared building blocks for the /register and /signin pages so the two stay in
5+
// visual lockstep. Mirrors the home page design language: warm tokens, a soft
6+
// radial glow for depth, and staggered fade-up entrances.
7+
8+
/** Page wrapper: centers the card and paints the hero-style radial glow behind it. */
9+
export function AuthShell({children}: {children: ReactNode}) {
10+
return (
11+
<div className="relative min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
12+
{/* Soft radial glow behind the card for depth — mirrors the home hero.
13+
Dark mode uses a brighter, lighter-hued, faster-falloff core so it reads
14+
as light, not brown haze. */}
15+
{/*<div*/}
16+
{/* aria-hidden*/}
17+
{/* className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[420px] bg-[radial-gradient(ellipse_60%_55%_at_50%_28%,rgba(193,127,62,0.16),transparent_70%)] dark:h-[360px] dark:bg-[radial-gradient(ellipse_48%_42%_at_50%_22%,rgba(224,162,102,0.22),rgba(193,127,62,0.06)_45%,transparent_64%)]"*/}
18+
{/*/>*/}
19+
<div className="max-w-md w-full space-y-8">{children}</div>
20+
</div>
21+
)
22+
}
23+
24+
/** Favicon + title + optional subtitle. */
25+
export function AuthHeader({title, subtitle}: {title: string; subtitle?: string}) {
26+
return (
27+
<div className="animate-fade-up">
28+
{/*<div className="flex justify-center mb-6">*/}
29+
{/* <FavIconBlack className="dark:invert" />*/}
30+
{/*</div>*/}
31+
<h2 className="text-center text-3xl font-extrabold tracking-tight text-ink-1000">{title}</h2>
32+
{subtitle && <p className="mt-3 text-center text-ink-600">{subtitle}</p>}
33+
</div>
34+
)
35+
}
36+
37+
/** The form element, with the standard entrance animation. */
38+
export function AuthForm({
39+
onSubmit,
40+
children,
41+
}: {
42+
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
43+
children: ReactNode
44+
}) {
45+
return (
46+
<form
47+
className="mt-8 space-y-6 animate-fade-up"
48+
style={{animationDelay: '80ms'}}
49+
onSubmit={onSubmit}
50+
>
51+
{children}
52+
</form>
53+
)
54+
}
55+
56+
/** Wraps stacked inputs so adjacent borders collapse into one. */
57+
export function AuthFieldGroup({children}: {children: ReactNode}) {
58+
return <div className="-space-y-px">{children}</div>
59+
}
60+
61+
const authInputBase =
62+
'bg-canvas-50 appearance-none relative block w-full px-4 py-3 border-[1.5px] border-canvas-200 text-ink-1000 placeholder-ink-400 transition-colors focus:outline-none focus:border-primary-500 focus:ring-1 focus:ring-primary-500 focus:z-10 sm:text-sm'
63+
64+
/** Email/password input with a screen-reader label. `position` rounds the outer corner. */
65+
export function AuthInput({
66+
position,
67+
label,
68+
below,
69+
className,
70+
...props
71+
}: {
72+
position: 'top' | 'bottom'
73+
label: string
74+
below?: ReactNode
75+
} & InputHTMLAttributes<HTMLInputElement>) {
76+
return (
77+
<div>
78+
<label htmlFor={props.id} className="sr-only">
79+
{label}
80+
</label>
81+
<input
82+
className={clsx(
83+
authInputBase,
84+
position === 'top' ? 'rounded-t-xl' : 'rounded-b-xl',
85+
className,
86+
)}
87+
{...props}
88+
/>
89+
{below}
90+
</div>
91+
)
92+
}
93+
94+
/** Gradient rule with a centered label. */
95+
export function AuthDivider({label}: {label: string}) {
96+
return (
97+
<div className="relative">
98+
<div className="absolute inset-0 flex items-center" aria-hidden>
99+
<div className="w-full h-px bg-gradient-to-r from-transparent via-canvas-300 to-transparent" />
100+
</div>
101+
<div className="relative flex justify-center text-sm">
102+
<span className="px-3 body-bg text-ink-500">{label}</span>
103+
</div>
104+
</div>
105+
)
106+
}
107+
108+
/** Primary CTA, matching the home hero button (amber, shadow, hover lift). */
109+
export function AuthSubmitButton({
110+
isLoading,
111+
children,
112+
}: {
113+
isLoading?: boolean
114+
children: ReactNode
115+
}) {
116+
return (
117+
<button
118+
type="submit"
119+
disabled={isLoading}
120+
className={clsx(
121+
'group relative w-full flex justify-center py-3.5 px-4 text-[15px] font-bold rounded-xl text-white bg-primary-500 shadow-[0_4px_16px_rgba(193,127,62,0.35)] transition-all duration-150 hover:bg-primary-600 hover:-translate-y-0.5 hover:shadow-[0_8px_24px_rgba(193,127,62,0.4)] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500',
122+
isLoading && 'opacity-70 cursor-not-allowed hover:translate-y-0',
123+
)}
124+
>
125+
{children}
126+
</button>
127+
)
128+
}
129+
130+
/** Centered error message. Renders nothing when empty. */
131+
export function AuthError({children}: {children: ReactNode}) {
132+
if (!children) return null
133+
return <div className="text-red-500 text-sm text-center">{children}</div>
134+
}
135+
136+
/** Footer line ("Already have an account?" etc.) with link styling + entrance. */
137+
export function AuthFooter({children}: {children: ReactNode}) {
138+
return (
139+
<div
140+
className="text-center text-ink-600 custom-link animate-fade-up"
141+
style={{animationDelay: '160ms'}}
142+
>
143+
<p>{children}</p>
144+
</div>
145+
)
146+
}

0 commit comments

Comments
 (0)