mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-07-30 17:59:13 -04:00
DRY and improve sign in/up design
This commit is contained in:
@@ -1326,6 +1326,7 @@
|
||||
"signin.seo.title": "Anmelden",
|
||||
"signin.submit": "Anmelden",
|
||||
"signin.title": "Anmelden",
|
||||
"signin.subtitle": "Willkommen zurück — mach da weiter, wo du aufgehört hast.",
|
||||
"signup.creating_profile": "Profil wird erstellt...",
|
||||
"signup.email": "E-Mail",
|
||||
"signup.email_placeholder": "sie@beispiel.de",
|
||||
|
||||
@@ -1325,6 +1325,7 @@
|
||||
"signin.seo.title": "Se connecter",
|
||||
"signin.submit": "Se connecter",
|
||||
"signin.title": "Se connecter",
|
||||
"signin.subtitle": "Bon retour — reprends là où tu t'étais arrêté.",
|
||||
"signup.creating_profile": "Création de votre profil...",
|
||||
"signup.email": "E-mail",
|
||||
"signup.email_placeholder": "vous@exemple.com",
|
||||
|
||||
146
web/components/auth/auth-form.tsx
Normal file
146
web/components/auth/auth-form.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import clsx from 'clsx'
|
||||
import {InputHTMLAttributes, ReactNode} from 'react'
|
||||
|
||||
// Shared building blocks for the /register and /signin pages so the two stay in
|
||||
// visual lockstep. Mirrors the home page design language: warm tokens, a soft
|
||||
// radial glow for depth, and staggered fade-up entrances.
|
||||
|
||||
/** Page wrapper: centers the card and paints the hero-style radial glow behind it. */
|
||||
export function AuthShell({children}: {children: ReactNode}) {
|
||||
return (
|
||||
<div className="relative min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
{/* Soft radial glow behind the card for depth — mirrors the home hero.
|
||||
Dark mode uses a brighter, lighter-hued, faster-falloff core so it reads
|
||||
as light, not brown haze. */}
|
||||
{/*<div*/}
|
||||
{/* aria-hidden*/}
|
||||
{/* 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%)]"*/}
|
||||
{/*/>*/}
|
||||
<div className="max-w-md w-full space-y-8">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Favicon + title + optional subtitle. */
|
||||
export function AuthHeader({title, subtitle}: {title: string; subtitle?: string}) {
|
||||
return (
|
||||
<div className="animate-fade-up">
|
||||
{/*<div className="flex justify-center mb-6">*/}
|
||||
{/* <FavIconBlack className="dark:invert" />*/}
|
||||
{/*</div>*/}
|
||||
<h2 className="text-center text-3xl font-extrabold tracking-tight text-ink-1000">{title}</h2>
|
||||
{subtitle && <p className="mt-3 text-center text-ink-600">{subtitle}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** The form element, with the standard entrance animation. */
|
||||
export function AuthForm({
|
||||
onSubmit,
|
||||
children,
|
||||
}: {
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<form
|
||||
className="mt-8 space-y-6 animate-fade-up"
|
||||
style={{animationDelay: '80ms'}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{children}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
/** Wraps stacked inputs so adjacent borders collapse into one. */
|
||||
export function AuthFieldGroup({children}: {children: ReactNode}) {
|
||||
return <div className="-space-y-px">{children}</div>
|
||||
}
|
||||
|
||||
const authInputBase =
|
||||
'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'
|
||||
|
||||
/** Email/password input with a screen-reader label. `position` rounds the outer corner. */
|
||||
export function AuthInput({
|
||||
position,
|
||||
label,
|
||||
below,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
position: 'top' | 'bottom'
|
||||
label: string
|
||||
below?: ReactNode
|
||||
} & InputHTMLAttributes<HTMLInputElement>) {
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor={props.id} className="sr-only">
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
className={clsx(
|
||||
authInputBase,
|
||||
position === 'top' ? 'rounded-t-xl' : 'rounded-b-xl',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
{below}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Gradient rule with a centered label. */
|
||||
export function AuthDivider({label}: {label: string}) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden>
|
||||
<div className="w-full h-px bg-gradient-to-r from-transparent via-canvas-300 to-transparent" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-3 body-bg text-ink-500">{label}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** Primary CTA, matching the home hero button (amber, shadow, hover lift). */
|
||||
export function AuthSubmitButton({
|
||||
isLoading,
|
||||
children,
|
||||
}: {
|
||||
isLoading?: boolean
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className={clsx(
|
||||
'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',
|
||||
isLoading && 'opacity-70 cursor-not-allowed hover:translate-y-0',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
/** Centered error message. Renders nothing when empty. */
|
||||
export function AuthError({children}: {children: ReactNode}) {
|
||||
if (!children) return null
|
||||
return <div className="text-red-500 text-sm text-center">{children}</div>
|
||||
}
|
||||
|
||||
/** Footer line ("Already have an account?" etc.) with link styling + entrance. */
|
||||
export function AuthFooter({children}: {children: ReactNode}) {
|
||||
return (
|
||||
<div
|
||||
className="text-center text-ink-600 custom-link animate-fade-up"
|
||||
style={{animationDelay: '160ms'}}
|
||||
>
|
||||
<p>{children}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -6,10 +6,21 @@ import Link from 'next/link'
|
||||
import {useSearchParams} from 'next/navigation'
|
||||
import React, {Suspense, useState} from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import {
|
||||
AuthDivider,
|
||||
AuthError,
|
||||
AuthFieldGroup,
|
||||
AuthFooter,
|
||||
AuthForm,
|
||||
AuthHeader,
|
||||
AuthInput,
|
||||
AuthShell,
|
||||
AuthSubmitButton,
|
||||
} from 'web/components/auth/auth-form'
|
||||
import {GoogleButton} from 'web/components/buttons/sign-up-button'
|
||||
import FavIconBlack from 'web/components/FavIcon'
|
||||
import {PageBase} from 'web/components/page-base'
|
||||
import {SEO} from 'web/components/SEO'
|
||||
import {NewTabLink} from 'web/components/widgets/new-tab-link'
|
||||
import {auth} from 'web/lib/firebase/users'
|
||||
import {useT} from 'web/lib/locale'
|
||||
import {googleSigninSignup, setOnboardingFlag, signinSignupRedirect} from 'web/lib/util/signup'
|
||||
@@ -112,146 +123,110 @@ function RegisterComponent() {
|
||||
description={t('register.seo.description', 'Register for a new account')}
|
||||
url={`/register`}
|
||||
/>
|
||||
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
{registrationSuccess ? (
|
||||
<div className="text-center">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-green-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="mt-6 text-3xl font-extrabold ">
|
||||
{t('register.check_email.title', 'Check your email')}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
{t('register.check_email.sent_prefix', 'We have sent a verification link to ')}
|
||||
<span className="font-medium">{registeredEmail}</span>
|
||||
{t('register.check_email.sent_suffix', '.')}
|
||||
</p>
|
||||
<p className="mt-4 text-sm text-gray-500">
|
||||
{t(
|
||||
'register.check_email.help_prefix',
|
||||
'Did not receive the email? Check your spam folder or ',
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="font-medium text-blue-600 hover:text-blue-500"
|
||||
onClick={() => setRegistrationSuccess(false)}
|
||||
>
|
||||
{t('register.check_email.try_again', 'try again')}
|
||||
</button>
|
||||
{t('register.check_email.help_suffix', '.')}
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
href="/signin"
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
||||
>
|
||||
{t('register.back_to_login', 'Back to Login')}
|
||||
</Link>
|
||||
</div>
|
||||
<AuthShell>
|
||||
{registrationSuccess ? (
|
||||
<div className="text-center">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
|
||||
<svg
|
||||
className="h-6 w-6 text-green-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div>
|
||||
{/*<h2 className="mt-6 text-center text-xl font-extrabold text-red-700">*/}
|
||||
{/* The project is still in development...*/}
|
||||
{/*</h2>*/}
|
||||
<div className="flex justify-center mb-6">
|
||||
<FavIconBlack className="dark:invert" />
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold ">
|
||||
{t('register.get_started', 'Get Started')}
|
||||
</h2>
|
||||
</div>
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
<label htmlFor="email" className="sr-only">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
className="bg-canvas-50 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-primary-500 focus:border-blue-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Email"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
className="bg-canvas-50 bg-input appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 rounded-b-md focus:outline-none focus:ring-primary-500 focus:border-blue-500 focus:z-10 sm:text-sm"
|
||||
placeholder={t('register.password_placeholder', 'Password')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm mt-2 text-center custom-link">
|
||||
{t('register.agreement.prefix', 'By signing up, I agree to the ')}
|
||||
<Link href="/terms">{t('register.terms', 'Terms and Conditions')}</Link>
|
||||
{t('register.agreement.and', ' and ')}
|
||||
<Link href="/privacy">{t('register.privacy', 'Privacy Policy')}</Link>
|
||||
{t('register.agreement.suffix', '.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{error && <div className="text-red-500 text-sm text-center">{error}</div>}
|
||||
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className={`group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-full text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 ${isLoading ? 'opacity-70 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{isLoading
|
||||
? t('register.button.creating', 'Creating account...')
|
||||
: t('register.button.email', 'Sign up with Email')}
|
||||
</button>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 body-bg text-gray-500">
|
||||
{t('register.or_sign_up_with', 'Or sign up with')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<GoogleButton onClick={googleSigninSignup} isLoading={isLoading} />
|
||||
</div>
|
||||
</form>
|
||||
<div className="my-8" />
|
||||
<div className="text-center custom-link">
|
||||
<p className="">
|
||||
{t('register.already_account', 'Already have an account?')}{' '}
|
||||
<Link href="/signin">{t('register.link_signin', 'Sign in')}</Link>
|
||||
</p>
|
||||
</div>
|
||||
<h2 className="mt-6 text-3xl font-extrabold ">
|
||||
{t('register.check_email.title', 'Check your email')}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
{t('register.check_email.sent_prefix', 'We have sent a verification link to ')}
|
||||
<span className="font-medium">{registeredEmail}</span>
|
||||
{t('register.check_email.sent_suffix', '.')}
|
||||
</p>
|
||||
<p className="mt-4 text-sm text-gray-500">
|
||||
{t(
|
||||
'register.check_email.help_prefix',
|
||||
'Did not receive the email? Check your spam folder or ',
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="font-medium text-blue-600 hover:text-blue-500"
|
||||
onClick={() => setRegistrationSuccess(false)}
|
||||
>
|
||||
{t('register.check_email.try_again', 'try again')}
|
||||
</button>
|
||||
{t('register.check_email.help_suffix', '.')}
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<Link
|
||||
href="/signin"
|
||||
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500"
|
||||
>
|
||||
{t('register.back_to_login', 'Back to Login')}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<AuthHeader
|
||||
title={t('register.get_started', 'Get Started')}
|
||||
subtitle={t('register.subtitle', 'Create your free account — no algorithms, no ads.')}
|
||||
/>
|
||||
<AuthForm onSubmit={handleSubmit}>
|
||||
<AuthFieldGroup>
|
||||
<AuthInput
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
position="top"
|
||||
label="Email"
|
||||
placeholder="Email"
|
||||
/>
|
||||
<AuthInput
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
position="bottom"
|
||||
label="Password"
|
||||
placeholder={t('register.password_placeholder', 'Password')}
|
||||
/>
|
||||
</AuthFieldGroup>
|
||||
|
||||
<p className="text-sm mt-2 text-center text-ink-600 custom-link">
|
||||
{t('register.agreement.prefix', 'By signing up, I agree to the ')}
|
||||
<NewTabLink href="/terms">{t('register.terms', 'Terms and Conditions')}</NewTabLink>
|
||||
{t('register.agreement.and', ' and ')}
|
||||
<NewTabLink href="/privacy">{t('register.privacy', 'Privacy Policy')}</NewTabLink>
|
||||
{t('register.agreement.suffix', '.')}
|
||||
</p>
|
||||
|
||||
<AuthError>{error}</AuthError>
|
||||
|
||||
<div className="space-y-4">
|
||||
<AuthSubmitButton isLoading={isLoading}>
|
||||
{isLoading
|
||||
? t('register.button.creating', 'Creating account...')
|
||||
: t('register.button.email', 'Sign up with Email')}
|
||||
</AuthSubmitButton>
|
||||
<AuthDivider label={t('register.or_sign_up_with', 'Or sign up with')} />
|
||||
<GoogleButton onClick={googleSigninSignup} isLoading={isLoading} />
|
||||
</div>
|
||||
</AuthForm>
|
||||
<AuthFooter>
|
||||
{t('register.already_account', 'Already have an account?')}{' '}
|
||||
<Link href="/signin">{t('register.link_signin', 'Sign in')}</Link>
|
||||
</AuthFooter>
|
||||
</>
|
||||
)}
|
||||
</AuthShell>
|
||||
</PageBase>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,18 @@ import {signInWithEmailAndPassword} from 'firebase/auth'
|
||||
import Link from 'next/link'
|
||||
import {useSearchParams} from 'next/navigation'
|
||||
import React, {Suspense, useEffect, useState} from 'react'
|
||||
import {
|
||||
AuthDivider,
|
||||
AuthError,
|
||||
AuthFieldGroup,
|
||||
AuthFooter,
|
||||
AuthForm,
|
||||
AuthHeader,
|
||||
AuthInput,
|
||||
AuthShell,
|
||||
AuthSubmitButton,
|
||||
} from 'web/components/auth/auth-form'
|
||||
import {GoogleButton} from 'web/components/buttons/sign-up-button'
|
||||
import FavIconBlack from 'web/components/FavIcon'
|
||||
import {InfoIcon} from 'web/components/icons'
|
||||
import {PageBase} from 'web/components/page-base'
|
||||
import {SEO} from 'web/components/SEO'
|
||||
@@ -129,55 +139,43 @@ function RegisterComponent() {
|
||||
description={t('signin.seo.description', 'Sign in to your account')}
|
||||
url={`/signin`}
|
||||
/>
|
||||
<div className="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md w-full space-y-8">
|
||||
{redirectPath && (
|
||||
<div className="bg-primary-100 border border-primary-200 rounded-lg p-4 flex items-center gap-3">
|
||||
<InfoIcon className="w-5 h-5 text-primary-700 flex-shrink-0" />
|
||||
<p className="text-primary-700 text-sm">
|
||||
{t(
|
||||
'signin.prompt.sign_in_to_access',
|
||||
'Please sign in to access the {redirectPath} page',
|
||||
{redirectPath: redirectPath.replace('/', '')},
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="flex justify-center mb-6">
|
||||
<FavIconBlack className="dark:invert" />
|
||||
</div>
|
||||
<h2 className="mt-6 text-center text-3xl font-extrabold ">
|
||||
{t('signin.title', 'Sign in')}
|
||||
</h2>
|
||||
<AuthShell>
|
||||
{redirectPath && (
|
||||
<div className="bg-primary-100 border border-primary-200 rounded-lg p-4 flex items-center gap-3">
|
||||
<InfoIcon className="w-5 h-5 text-primary-700 flex-shrink-0" />
|
||||
<p className="text-primary-700 text-sm">
|
||||
{t(
|
||||
'signin.prompt.sign_in_to_access',
|
||||
'Please sign in to access the {redirectPath} page',
|
||||
{redirectPath: redirectPath.replace('/', '')},
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
<div className="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
<label htmlFor="email" className="sr-only">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
className="bg-canvas-50 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-t-md border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-primary-500 focus:border-blue-500 focus:z-10 sm:text-sm"
|
||||
placeholder="Email"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
className="bg-canvas-50 appearance-none rounded-none relative block w-full px-3 py-2 border rounded-b-md border-gray-300 placeholder-gray-500 focus:outline-none focus:ring-primary-500 focus:border-blue-500 focus:z-10 sm:text-sm"
|
||||
placeholder={t('signin.password_placeholder', 'Your password')}
|
||||
/>
|
||||
)}
|
||||
<AuthHeader
|
||||
title={t('signin.title', 'Sign in')}
|
||||
subtitle={t('signin.subtitle', 'Welcome back — pick up where you left off.')}
|
||||
/>
|
||||
<AuthForm onSubmit={handleSubmit}>
|
||||
<AuthFieldGroup>
|
||||
<AuthInput
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
position="top"
|
||||
label="Email"
|
||||
placeholder="Email"
|
||||
/>
|
||||
<AuthInput
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
position="bottom"
|
||||
label="Password"
|
||||
placeholder={t('signin.password_placeholder', 'Your password')}
|
||||
below={
|
||||
<div className="text-right mt-1 custom-link">
|
||||
<button
|
||||
type="button"
|
||||
@@ -201,41 +199,25 @@ function RegisterComponent() {
|
||||
{t('signin.forgot_password', 'Forgot password?')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</AuthFieldGroup>
|
||||
|
||||
{error && <div className="text-red-500 text-sm text-center">{error}</div>}
|
||||
<AuthError>{error}</AuthError>
|
||||
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className={`group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-full text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 ${isLoading ? 'opacity-70 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
{isLoading ? 'Signing in...' : t('signin.submit', 'Sign in with Email')}
|
||||
</button>
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 body-bg text-gray-500">
|
||||
{t('signin.continue', 'Or continue with')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<GoogleButton onClick={handleGoogleSignIn} isLoading={isLoading} />
|
||||
</div>
|
||||
</form>
|
||||
<div className="text-center custom-link">
|
||||
<p className="mt-4 text-sm">
|
||||
{t('signin.no_account', "Don't have an account?")}{' '}
|
||||
<Link href="/register">{t('signin.link_sign_up', 'Register')}</Link>
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
<AuthSubmitButton isLoading={isLoading}>
|
||||
{isLoading ? 'Signing in...' : t('signin.submit', 'Sign in with Email')}
|
||||
</AuthSubmitButton>
|
||||
<AuthDivider label={t('signin.continue', 'Or continue with')} />
|
||||
<GoogleButton onClick={handleGoogleSignIn} isLoading={isLoading} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AuthForm>
|
||||
<AuthFooter>
|
||||
{t('signin.no_account', "Don't have an account?")}{' '}
|
||||
<Link href="/register">{t('signin.link_sign_up', 'Register')}</Link>
|
||||
</AuthFooter>
|
||||
</AuthShell>
|
||||
</PageBase>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user