style: upgrade sign-up page UI to match sign-in page aesthetics

- Add brand gradient title, status badge, and descriptive subtitle
- Polished form inputs: h-12 rounded-xl with icons matching sign-in
- Password fields in 2-column grid layout on desktop
- Brand gradient submit button with hover/active animations
- Styled verification code row with matching heights
- Consistent uppercase tracking labels throughout
- "Already registered?" section with link matching sign-in's footer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 17:58:04 +08:00
co-authored by Claude Opus 4.6
parent e6e80c6a18
commit 330b99061c
2 changed files with 144 additions and 71 deletions
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
import type { z } from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { Loader2 } from 'lucide-react'
import { Loader2, User, Mail, KeyRound, ShieldCheck, UserPlus } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { cn } from '@/lib/utils'
@@ -212,88 +212,121 @@ export function SignUpForm({
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className={cn('grid gap-4', className)}
className={cn('space-y-5', className)}
{...props}
>
{/* Username Field */}
<FormField
control={form.control}
name='username'
render={({ field }) => (
<FormItem>
<FormLabel>{t('Username')}</FormLabel>
<FormLabel className='text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground'>
{t('Username')}
</FormLabel>
<FormControl>
<Input placeholder={t('Enter your username')} {...field} />
<div className='relative'>
<User className='pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground' />
<Input
placeholder={t('Enter your username')}
autoComplete='username'
className='h-12 rounded-xl border-[color-mix(in_oklch,var(--primary)_28%,var(--border))] bg-[color-mix(in_oklch,var(--background)_82%,transparent)] pl-10 text-sm shadow-[inset_0_1px_0_color-mix(in_oklch,var(--primary)_18%,transparent)] focus-visible:ring-primary/40'
{...field}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Password Field */}
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem>
<FormLabel>{t('Password')}</FormLabel>
<FormControl>
<PasswordInput
placeholder={t('Enter password (8-20 characters)')}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className='grid gap-5 sm:grid-cols-2'>
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem>
<FormLabel className='text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground'>
{t('Password')}
</FormLabel>
<FormControl>
<div className='relative'>
<KeyRound className='pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground' />
<PasswordInput
placeholder={t('8-20 characters')}
autoComplete='new-password'
className='h-12 rounded-xl border-[color-mix(in_oklch,var(--primary)_28%,var(--border))] bg-[color-mix(in_oklch,var(--background)_82%,transparent)] pl-10 text-sm shadow-[inset_0_1px_0_color-mix(in_oklch,var(--primary)_18%,transparent)] focus-visible:ring-primary/40'
{...field}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Confirm Password Field */}
<FormField
control={form.control}
name='confirmPassword'
render={({ field }) => (
<FormItem>
<FormLabel>{t('Confirm password')}</FormLabel>
<FormControl>
<PasswordInput placeholder={t('Confirm password')} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='confirmPassword'
render={({ field }) => (
<FormItem>
<FormLabel className='text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground'>
{t('Confirm password')}
</FormLabel>
<FormControl>
<div className='relative'>
<ShieldCheck className='pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground' />
<PasswordInput
placeholder={t('Confirm password')}
autoComplete='new-password'
className='h-12 rounded-xl border-[color-mix(in_oklch,var(--primary)_28%,var(--border))] bg-[color-mix(in_oklch,var(--background)_82%,transparent)] pl-10 text-sm shadow-[inset_0_1px_0_color-mix(in_oklch,var(--primary)_18%,transparent)] focus-visible:ring-primary/40'
{...field}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Email Verification Section */}
{emailVerificationRequired && (
<>
{/* Email Field */}
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem>
<FormLabel>
{t('Email (required for verification)')}
<FormLabel className='text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground'>
{t('Email')}
</FormLabel>
<FormControl>
<Input
placeholder={t('name@example.com')}
type='email'
{...field}
/>
<div className='relative'>
<Mail className='pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground' />
<Input
placeholder={t('name@example.com')}
type='email'
autoComplete='email'
className='h-12 rounded-xl border-[color-mix(in_oklch,var(--primary)_28%,var(--border))] bg-[color-mix(in_oklch,var(--background)_82%,transparent)] pl-10 text-sm shadow-[inset_0_1px_0_color-mix(in_oklch,var(--primary)_18%,transparent)] focus-visible:ring-primary/40'
{...field}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Verification Code Field */}
<div className='flex items-end gap-2'>
<div className='flex items-end gap-3'>
<div className='flex-1'>
<label className='mb-2 block text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground'>
{t('Verification code')}
</label>
<Input
placeholder={t('Verification code')}
placeholder={t('Enter code')}
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
autoComplete='one-time-code'
className='h-12 rounded-xl border-[color-mix(in_oklch,var(--primary)_28%,var(--border))] bg-[color-mix(in_oklch,var(--background)_82%,transparent)] text-sm shadow-[inset_0_1px_0_color-mix(in_oklch,var(--primary)_18%,transparent)] focus-visible:ring-primary/40'
/>
</div>
<Button
@@ -301,6 +334,7 @@ export function SignUpForm({
type='button'
disabled={isLoading || isSendingCode || isActive || !emailValue}
onClick={handleSendVerificationCode}
className='h-12 shrink-0 rounded-xl border-[color-mix(in_oklch,var(--primary)_28%,var(--border))] px-5 text-sm font-semibold'
>
{isActive ? (
t('Resend ({{seconds}}s)', { seconds: secondsLeft })
@@ -312,14 +346,11 @@ export function SignUpForm({
</Button>
</div>
{/* Turnstile */}
{isTurnstileEnabled && (
<div className='mt-2'>
<Turnstile
siteKey={turnstileSiteKey}
onVerify={setTurnstileToken}
/>
</div>
<Turnstile
siteKey={turnstileSiteKey}
onVerify={setTurnstileToken}
/>
)}
</>
)}
@@ -331,15 +362,28 @@ export function SignUpForm({
className='mt-1'
/>
{/* Submit Button */}
<Button
className='mt-2 w-full justify-center gap-2'
type='submit'
className='h-12 w-full justify-center gap-2 rounded-xl text-sm font-semibold text-white shadow-[0_18px_48px_-18px_rgba(123,107,227,0.65)] transition-transform hover:translate-y-[-1px] hover:shadow-[0_22px_56px_-18px_rgba(123,107,227,0.75)] active:translate-y-0 active:scale-[0.99]'
style={{
backgroundImage: 'var(--gradient-brand-btn)',
backgroundColor: 'var(--brand)',
border: '1px solid rgba(255,255,255,0.16)',
}}
disabled={isLoading || (requiresLegalConsent && !agreedToLegal)}
>
{isLoading ? <Loader2 className='h-4 w-4 animate-spin' /> : null}
{isLoading ? (
<Loader2 className='h-4 w-4 animate-spin' />
) : (
<UserPlus className='h-4 w-4' />
)}
{t('Create account')}
</Button>
<p className='text-center text-[11px] tracking-[0.12em] text-muted-foreground uppercase'>
{t('Verified via Heicode identity service')}
</p>
{oauthRegisterEnabled && (
<OAuthProviders
status={status}
+42 -13
View File
@@ -1,5 +1,6 @@
import { Link } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next'
import { ArrowUpRight } from 'lucide-react'
import { useStatus } from '@/hooks/use-status'
import { AuthLayout } from '../auth-layout'
import { TermsFooter } from '../components/terms-footer'
@@ -11,25 +12,53 @@ export function SignUp() {
return (
<AuthLayout>
<div className='w-full space-y-8'>
<div className='space-y-2'>
<h2 className='text-center text-2xl font-semibold tracking-tight sm:text-left'>
{t('Create an account')}
<div className='space-y-8'>
<div className='space-y-3'>
<span
className='inline-flex items-center gap-2 rounded-full px-3 py-1 text-[11px] font-semibold tracking-[0.18em] uppercase'
style={{
border: '1px solid rgba(123,107,227,0.35)',
backgroundColor: 'rgba(123,107,227,0.10)',
color: 'var(--brand)',
}}
>
<span
className='inline-block h-1.5 w-1.5 rounded-full'
style={{ backgroundColor: 'var(--brand)' }}
/>
{t('New account')}
</span>
<h2
className='text-3xl font-semibold tracking-tight sm:text-4xl'
style={{
backgroundImage: 'var(--gradient-brand)',
WebkitBackgroundClip: 'text',
backgroundClip: 'text',
color: 'transparent',
}}
>
{t('Create your account')}
</h2>
<p className='text-muted-foreground text-left text-sm sm:text-base'>
{t('Already have an account?')}{' '}
<Link
to='/sign-in'
className='hover:text-primary font-medium underline underline-offset-4'
>
{t('Sign in')}
</Link>
.
<p className='text-sm text-muted-foreground sm:text-base'>
{t('Register to access the Heicode platform. Email verification is required to complete your account setup.')}
</p>
</div>
<SignUpForm />
<div className='space-y-3 border-t border-dashed border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] pt-5'>
<p className='text-xs font-semibold tracking-[0.16em] text-muted-foreground uppercase'>
{t('Already registered?')}
</p>
<Link
to='/sign-in'
className='inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline'
>
{t('Sign in to your workspace')}
<ArrowUpRight className='h-3.5 w-3.5' />
</Link>
</div>
<TermsFooter
variant='sign-up'
status={status}