login de cadastro padronizado
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-11-09 15:05:20 +02:00
parent 67aa015b4c
commit 2209b83820
7 changed files with 463 additions and 175 deletions
@@ -0,0 +1,49 @@
import type { ReactNode } from 'react';
type Props = {
badge: string;
title: string;
description: string;
children: ReactNode;
footer: ReactNode;
};
export default function AuthTempoCard({
badge,
title,
description,
children,
footer,
}: Props) {
return (
<div className="w-full overflow-hidden rounded-3xl bg-slate-50 shadow-2xl ring-1 ring-black/5">
<div className="p-5 sm:p-8 lg:p-10">
<img
src="/images/logo/logo-vermelho.png"
alt="Tempo"
width="1787"
height="564"
className="mx-auto mb-4 h-14 w-auto"
/>
<div className="mx-auto mb-8 max-w-2xl text-center">
<span className="mb-3 inline-flex rounded-full bg-red-100 px-3 py-1 text-xs font-bold tracking-wide text-[#b51219] uppercase">
{badge}
</span>
<h2 className="text-2xl font-bold text-slate-950 sm:text-3xl">
{title}
</h2>
<p className="mt-3 text-sm leading-6 text-slate-600">
{description}
</p>
</div>
{children}
</div>
<div className="border-t border-slate-200 bg-white p-5 text-center text-sm text-slate-600">
{footer}
</div>
</div>
);
}
+9 -1
View File
@@ -6,6 +6,7 @@ import InputError from '@/components/input-error';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { Spinner } from '@/components/ui/spinner';
import { cn } from '@/lib/utils';
type Props = {
routes?: {
@@ -15,6 +16,7 @@ type Props = {
label?: string;
loadingLabel?: string;
separator?: string;
separatorClassName?: string;
};
export default function PasskeyVerify({
@@ -22,6 +24,7 @@ export default function PasskeyVerify({
label,
loadingLabel,
separator,
separatorClassName,
}: Props = {}) {
const { verify, isLoading, error, isSupported } = usePasskeyVerify({
...(routes && {
@@ -64,7 +67,12 @@ export default function PasskeyVerify({
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">
<span
className={cn(
'bg-background text-muted-foreground px-2',
separatorClassName,
)}
>
{separator ?? 'Ou continuar com email'}
</span>
</div>
+142 -28
View File
@@ -243,39 +243,153 @@ const translations: Record<string, Record<string, string>> = {
const sharedTranslations: Record<string, [string, string, string]> = {
'Criar conta': ['Create account', 'Créer un compte', 'Crear cuenta'],
'Entrar na sua conta': ['Sign in to your account', 'Connectez-vous à votre compte', 'Inicie sesión en su cuenta'],
'Crie uma conta simples para continuar e acompanhar a sua operação. Não é necessário configurar MFA para participar.': ['Create an account to continue and follow your operation. You do not need to set up MFA to participate.', 'Créez un compte pour poursuivre et suivre votre opération. La configuration de l’authentification multifacteur n’est pas nécessaire pour participer.', 'Cree una cuenta para continuar y seguir su operación. No necesita configurar la autenticación multifactor para participar.'],
'Entre para continuar a operação que escolheu. Os dados preenchidos permanecem guardados neste navegador.': ['Sign in to continue the operation you chose. Your entered data remains saved in this browser.', 'Connectez-vous pour poursuivre l’opération choisie. Les données saisies restent enregistrées dans ce navigateur.', 'Inicie sesión para continuar la operación elegida. Los datos introducidos permanecen guardados en este navegador.'],
'Entrar na sua conta': [
'Sign in to your account',
'Connectez-vous à votre compte',
'Inicie sesión en su cuenta',
],
'Área reservada': ['Private area', 'Espace réservé', 'Área reservada'],
'Bolsa de Parcerias, Investidores e Capital': [
'Partnership, Investor and Capital Exchange',
'Bourse des partenariats, investisseurs et capitaux',
'Bolsa de Alianzas, Inversores y Capital',
],
'Aceda à sua conta Tempo.': [
'Access your Tempo account.',
'Accédez à votre compte Tempo.',
'Acceda a su cuenta Tempo.',
],
'Crie uma conta simples para continuar e acompanhar a sua operação. Não é necessário configurar MFA para participar.':
[
'Create an account to continue and follow your operation. You do not need to set up MFA to participate.',
'Créez un compte pour poursuivre et suivre votre opération. La configuration de l’authentification multifacteur n’est pas nécessaire pour participer.',
'Cree una cuenta para continuar y seguir su operación. No necesita configurar la autenticación multifactor para participar.',
],
'Entre para continuar a operação que escolheu. Os dados preenchidos permanecem guardados neste navegador.':
[
'Sign in to continue the operation you chose. Your entered data remains saved in this browser.',
'Connectez-vous pour poursuivre l’opération choisie. Les données saisies restent enregistrées dans ce navigateur.',
'Inicie sesión para continuar la operación elegida. Los datos introducidos permanecen guardados en este navegador.',
],
'Palavra-passe': ['Password', 'Mot de passe', 'Contraseña'],
'Mostrar palavra-passe': ['Show password', 'Afficher le mot de passe', 'Mostrar contraseña'],
'Ocultar palavra-passe': ['Hide password', 'Masquer le mot de passe', 'Ocultar contraseña'],
'Confirmar palavra-passe': ['Confirm password', 'Confirmer le mot de passe', 'Confirmar contraseña'],
'Nome': ['Name', 'Nom', 'Nombre'],
'Mostrar palavra-passe': [
'Show password',
'Afficher le mot de passe',
'Mostrar contraseña',
],
'Ocultar palavra-passe': [
'Hide password',
'Masquer le mot de passe',
'Ocultar contraseña',
],
'Confirmar palavra-passe': [
'Confirm password',
'Confirmer le mot de passe',
'Confirmar contraseña',
],
Nome: ['Name', 'Nom', 'Nombre'],
'Nome completo': ['Full name', 'Nom complet', 'Nombre completo'],
'Esqueceu a palavra-passe?': ['Forgot your password?', 'Mot de passe oublié ?', '¿Olvidó su contraseña?'],
'Manter sessão iniciada': ['Keep me signed in', 'Rester connecté', 'Mantener la sesión iniciada'],
'Ainda não tem conta?': ['Don’t have an account?', 'Vous n’avez pas de compte ?', '¿Aún no tiene cuenta?'],
'Já tem conta?': ['Already have an account?', 'Vous avez déjà un compte ?', '¿Ya tiene cuenta?'],
'Entrar com chave de acesso': ['Sign in with a passkey', 'Se connecter avec une clé d’accès', 'Iniciar sesión con clave de acceso'],
'A autenticar…': ['Signing in…', 'Connexion en cours…', 'Iniciando sesión…'],
'Ou continuar com email': ['Or continue with email', 'Ou continuer avec l’e-mail', 'O continuar con correo electrónico'],
'O email ou a palavra-passe não correspondem aos nossos registos.': ['The email or password does not match our records.', 'L’adresse e-mail ou le mot de passe ne correspond pas à nos informations.', 'El correo electrónico o la contraseña no coinciden con nuestros registros.'],
'A palavra-passe está incorrecta.': ['The password is incorrect.', 'Le mot de passe est incorrect.', 'La contraseña es incorrecta.'],
'Atalho para o conteúdo': ['Skip link', 'Lien d’accès rapide', 'Enlace de acceso rápido'],
'Saltar para o conteúdo': ['Skip to content', 'Aller au contenu', 'Ir al contenido'],
'Caminho de navegação': ['Breadcrumb', 'Fil d’Ariane', 'Ruta de navegación'],
'Contacto de apoio ao investidor': ['Investor support contact', 'Contact de l’assistance aux investisseurs', 'Contacto de apoyo al inversor'],
'Contactar Apoio ao Investidor': ['Contact investor support', 'Contacter l’assistance aux investisseurs', 'Contactar con apoyo al inversor'],
'Ambiente de homologação': ['Review environment', 'Environnement de validation', 'Entorno de validación'],
'Homologação reservada à revisão da Tempo. Consulte o conteúdo público; entre na sua conta quando iniciar uma operação.': ['This review environment is reserved for Tempo. Browse public content; sign in when starting an operation.', 'Cet environnement de validation est réservé à Tempo. Consultez le contenu public ; connectez-vous pour commencer une opération.', 'Este entorno de validación está reservado para Tempo. Consulte el contenido público; inicie sesión al comenzar una operación.'],
'Idioma da navegação': ['Navigation language', 'Langue de navigation', 'Idioma de navegación'],
'Esqueceu a palavra-passe?': [
'Forgot your password?',
'Mot de passe oublié ?',
'¿Olvidó su contraseña?',
],
'Manter sessão iniciada': [
'Keep me signed in',
'Rester connecté',
'Mantener la sesión iniciada',
],
'Ainda não tem conta?': [
'Don’t have an account?',
'Vous n’avez pas de compte ?',
'¿Aún no tiene cuenta?',
],
'Já tem conta?': [
'Already have an account?',
'Vous avez déjà un compte ?',
'¿Ya tiene cuenta?',
],
'Entrar com chave de acesso': [
'Sign in with a passkey',
'Se connecter avec une clé d’accès',
'Iniciar sesión con clave de acceso',
],
'A autenticar…': [
'Signing in…',
'Connexion en cours…',
'Iniciando sesión…',
],
'Ou continuar com email': [
'Or continue with email',
'Ou continuer avec l’e-mail',
'O continuar con correo electrónico',
],
'O email ou a palavra-passe não correspondem aos nossos registos.': [
'The email or password does not match our records.',
'L’adresse e-mail ou le mot de passe ne correspond pas à nos informations.',
'El correo electrónico o la contraseña no coinciden con nuestros registros.',
],
'A palavra-passe está incorrecta.': [
'The password is incorrect.',
'Le mot de passe est incorrect.',
'La contraseña es incorrecta.',
],
'Atalho para o conteúdo': [
'Skip link',
'Lien d’accès rapide',
'Enlace de acceso rápido',
],
'Saltar para o conteúdo': [
'Skip to content',
'Aller au contenu',
'Ir al contenido',
],
'Caminho de navegação': [
'Breadcrumb',
'Fil d’Ariane',
'Ruta de navegación',
],
'Contacto de apoio ao investidor': [
'Investor support contact',
'Contact de l’assistance aux investisseurs',
'Contacto de apoyo al inversor',
],
'Contactar Apoio ao Investidor': [
'Contact investor support',
'Contacter l’assistance aux investisseurs',
'Contactar con apoyo al inversor',
],
'Ambiente de homologação': [
'Review environment',
'Environnement de validation',
'Entorno de validación',
],
'Homologação reservada à revisão da Tempo. Consulte o conteúdo público; entre na sua conta quando iniciar uma operação.':
[
'This review environment is reserved for Tempo. Browse public content; sign in when starting an operation.',
'Cet environnement de validation est réservé à Tempo. Consultez le contenu public ; connectez-vous pour commencer une opération.',
'Este entorno de validación está reservado para Tempo. Consulte el contenido público; inicie sesión al comenzar una operación.',
],
'Idioma da navegação': [
'Navigation language',
'Langue de navigation',
'Idioma de navegación',
],
'Navegação institucional': ['Language', 'Langue', 'Idioma'],
'Valores': ['Values', 'Valeurs', 'Valores'],
Valores: ['Values', 'Valeurs', 'Valores'],
'Ver menos': ['Show less', 'Voir moins', 'Ver menos'],
'Ver mais': ['Show more', 'Voir plus', 'Ver más'],
'Voltar': ['Back', 'Retour', 'Volver'],
'Anos de Experiência': ['Years of experience', 'Années d’expérience', 'Años de experiencia'],
'Projetos Realizados': ['Projects completed', 'Projets réalisés', 'Proyectos realizados'],
Voltar: ['Back', 'Retour', 'Volver'],
'Anos de Experiência': [
'Years of experience',
'Années d’expérience',
'Años de experiencia',
],
'Projetos Realizados': [
'Projects completed',
'Projets réalisés',
'Proyectos realizados',
],
};
for (const [source, values] of Object.entries(sharedTranslations)) {
(['en', 'fr', 'es'] as const).forEach((locale, index) => {
+8
View File
@@ -1,16 +1,24 @@
import AuthLayoutTemplate from '@/layouts/auth/auth-simple-layout';
import AuthTempoLayout from '@/layouts/auth/auth-tempo-layout';
import { useTempoUi } from '@/components/tempo-brand';
export default function AuthLayout({
title = '',
description = '',
variant = 'simple',
children,
}: {
title?: string;
description?: string;
variant?: 'simple' | 'tempo-card';
children: React.ReactNode;
}) {
const { t } = useTempoUi();
if (variant === 'tempo-card') {
return <AuthTempoLayout>{children}</AuthTempoLayout>;
}
return (
<AuthLayoutTemplate title={t(title)} description={t(description)}>
{children}
@@ -0,0 +1,9 @@
import type { PropsWithChildren } from 'react';
export default function AuthTempoLayout({ children }: PropsWithChildren) {
return (
<div className="flex min-h-[70vh] items-center justify-center bg-slate-100 px-4 py-8 sm:py-12">
<div className="w-full max-w-md">{children}</div>
</div>
);
}
+178 -114
View File
@@ -1,6 +1,7 @@
import { participationIdentity } from '@/lib/participation-identity';
import { useTempoUi } from '@/components/tempo-brand';
import { Form, Head, usePage } from '@inertiajs/react';
import { Check, LogIn } from 'lucide-react';
import InputError from '@/components/input-error';
import PasswordInput from '@/components/password-input';
import TextLink from '@/components/text-link';
@@ -24,6 +25,13 @@ type Props = {
qaAccount?: { id: number; email: string };
};
const controlClass =
'h-auto rounded-xl border-slate-300 bg-white px-3.5 py-3 text-sm text-slate-900 shadow-sm placeholder:text-slate-400 hover:border-slate-400 focus-visible:border-[#ed1c24] focus-visible:ring-4 focus-visible:ring-[#ed1c24]/10';
const labelClass =
'mb-1.5 block text-xs leading-normal font-bold text-slate-800';
const inlineLabelClass =
'block text-xs leading-normal font-bold text-slate-800';
export default function Login({ status, canResetPassword, qaAccount }: Props) {
const { t } = useTempoUi();
// A redirect from another page (e.g. MFA) creates a new Form with empty local errors.
@@ -32,130 +40,186 @@ export default function Login({ status, canResetPassword, qaAccount }: Props) {
<>
<Head title={t('Entrar')} />
{qaAccount && (
<aside
className="mb-4 rounded border border-amber-300 bg-amber-50 p-3 text-sm text-slate-900"
aria-label="Conta de ensaio QA"
>
<p className="font-semibold">
Ambiente QA — conta {qaAccount.id}
</p>
<p>
Para os cenários A/B: {qaAccount.email}. Use a
palavra-passe e o autenticador desta conta. Outros
perfis devem usar as respetivas contas sintéticas QA.
</p>
</aside>
)}
<div className="w-full overflow-hidden rounded-3xl bg-slate-50 shadow-2xl ring-1 ring-black/5">
<div className="p-5 sm:p-8 lg:p-10">
<img
src="/images/logo/logo-vermelho.png"
alt="Tempo"
width="1787"
height="564"
className="mx-auto mb-4 h-14 w-auto"
/>
{/* @chisel-passkeys */}
<PasskeyVerify
label={t('Entrar com chave de acesso')}
loadingLabel={t('A autenticar…')}
separator={t('Ou continuar com email')}
/>
{/* @end-chisel-passkeys */}
<div className="mx-auto mb-8 max-w-2xl text-center">
<span className="mb-3 inline-flex rounded-full bg-red-100 px-3 py-1 text-xs font-bold tracking-wide text-[#b51219] uppercase">
{t('Área reservada')}
</span>
<h2 className="text-2xl font-bold text-slate-950 sm:text-3xl">
{t('Entrar na sua conta')}
</h2>
<p className="mt-3 text-sm leading-6 text-slate-600">
{t('Aceda à sua conta Tempo.')}
</p>
</div>
<Form
{...store.form()}
resetOnSuccess={['password']}
className="flex flex-col gap-6"
>
{({ processing, errors }) => (
<>
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
name="email"
required
tabIndex={1}
autoComplete="email"
placeholder="email@example.com"
defaultValue={
qaAccount?.email ??
participationIdentity().email
}
/>
<InputError
message={errors.email ?? pageErrors.email}
/>
</div>
{qaAccount && (
<aside
className="mb-5 rounded-xl border border-amber-300 bg-amber-50 p-4 text-sm text-slate-900"
aria-label="Conta de ensaio QA"
>
<p className="font-semibold">
Ambiente QA — conta {qaAccount.id}
</p>
<p>
Para os cenários A/B: {qaAccount.email}. Use a
palavra-passe e o autenticador desta conta.
Outros perfis devem usar as respetivas contas
sintéticas QA.
</p>
</aside>
)}
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">
{t('Palavra-passe')}
</Label>
{canResetPassword && (
<TextLink
href={request()}
className="ml-auto text-sm"
tabIndex={5}
{status && (
<div
role="status"
className="mb-5 flex gap-2 rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm font-medium text-emerald-800"
>
<Check className="shrink-0" size={18} />
{status}
</div>
)}
{/* @chisel-passkeys */}
<PasskeyVerify
label={t('Entrar com chave de acesso')}
loadingLabel={t('A autenticar…')}
separator={t('Ou continuar com email')}
separatorClassName="bg-slate-50"
/>
{/* @end-chisel-passkeys */}
<Form
{...store.form()}
resetOnSuccess={['password']}
className="flex flex-col gap-5"
>
{({ processing, errors }) => (
<>
<div className="grid gap-5">
<div className="grid gap-1.5">
<Label
htmlFor="email"
className={labelClass}
>
{t('Esqueceu a palavra-passe?')}
</TextLink>
)}
Email
</Label>
<Input
id="email"
type="email"
name="email"
required
tabIndex={1}
autoComplete="email"
placeholder="email@example.com"
className={controlClass}
defaultValue={
qaAccount?.email ??
participationIdentity().email
}
/>
<InputError
message={
errors.email ?? pageErrors.email
}
/>
</div>
<div className="grid gap-1.5">
<div className="flex items-center">
<Label
htmlFor="password"
className={inlineLabelClass}
>
{t('Palavra-passe')}
</Label>
{canResetPassword && (
<TextLink
href={request()}
className="ml-auto text-xs font-bold text-[#ed1c24] no-underline hover:underline"
tabIndex={5}
>
{t(
'Esqueceu a palavra-passe?',
)}
</TextLink>
)}
</div>
<PasswordInput
id="password"
name="password"
required
tabIndex={2}
autoComplete="current-password"
placeholder={t('Palavra-passe')}
className={`${controlClass} pr-10`}
/>
<InputError message={errors.password} />
</div>
<div className="flex items-center space-x-3">
<Checkbox
id="remember"
name="remember"
tabIndex={3}
className="size-4 border-slate-300 data-[state=checked]:border-[#ed1c24] data-[state=checked]:bg-[#ed1c24] data-[state=checked]:text-white"
/>
<Label
htmlFor="remember"
className="text-sm font-medium text-slate-700"
>
{t('Manter sessão iniciada')}
</Label>
</div>
<Button
type="submit"
className="mt-1 flex w-full items-center justify-center gap-2 rounded-xl bg-[#ed1c24] px-6 py-4 font-bold text-white shadow-lg shadow-red-900/15 transition hover:-translate-y-0.5 hover:bg-[#c6161d] hover:shadow-xl disabled:translate-y-0 disabled:cursor-not-allowed disabled:opacity-60"
tabIndex={4}
disabled={processing}
data-test="login-button"
>
{processing ? (
<Spinner />
) : (
<LogIn size={18} />
)}
{t('Entrar')}
</Button>
</div>
<PasswordInput
id="password"
name="password"
required
tabIndex={2}
autoComplete="current-password"
placeholder={t('Palavra-passe')}
/>
<InputError message={errors.password} />
</div>
<div className="flex items-center space-x-3">
<Checkbox
id="remember"
name="remember"
tabIndex={3}
/>
<Label htmlFor="remember">
{t('Manter sessão iniciada')}
</Label>
</div>
<Button
type="submit"
className="mt-4 w-full"
tabIndex={4}
disabled={processing}
data-test="login-button"
>
{processing && <Spinner />}
{t('Entrar')}
</Button>
</div>
{/* @chisel-registration */}
<div className="text-muted-foreground text-center text-sm">
{t('Ainda não tem conta?')}{' '}
<TextLink href={register()} tabIndex={5}>
{t('Criar conta')}
</TextLink>
</div>
{/* @end-chisel-registration */}
</>
)}
</Form>
{status && (
<div className="mb-4 text-center text-sm font-medium text-green-600">
{status}
</>
)}
</Form>
</div>
)}
{/* @chisel-registration */}
<div className="border-t border-slate-200 bg-white p-5 text-center text-sm text-slate-600">
{t('Ainda não tem conta?')}{' '}
<TextLink
href={register()}
tabIndex={5}
className="font-bold text-[#ed1c24] no-underline hover:underline"
>
{t('Criar conta')}
</TextLink>
</div>
{/* @end-chisel-registration */}
</div>
</>
);
}
Login.layout = {
variant: 'tempo-card',
title: 'Entrar na sua conta',
description:
'Entre para continuar a operação que escolheu. Os dados preenchidos permanecem guardados neste navegador.',
description: 'Aceda à sua conta Tempo.',
};
+68 -32
View File
@@ -1,6 +1,8 @@
import { participationIdentity } from '@/lib/participation-identity';
import { useTempoUi } from '@/components/tempo-brand';
import { Form, Head } from '@inertiajs/react';
import { UserPlus } from 'lucide-react';
import AuthTempoCard from '@/components/auth-tempo-card';
import InputError from '@/components/input-error';
import PasswordInput from '@/components/password-input';
import TextLink from '@/components/text-link';
@@ -15,22 +17,47 @@ type Props = {
passwordRules: string;
};
const controlClass =
'h-auto rounded-xl border-slate-300 bg-white px-3.5 py-3 text-sm text-slate-900 shadow-sm placeholder:text-slate-400 hover:border-slate-400 focus-visible:border-[#ed1c24] focus-visible:ring-4 focus-visible:ring-[#ed1c24]/10';
const labelClass =
'mb-1.5 block text-xs leading-normal font-bold text-slate-800';
export default function Register({ passwordRules }: Props) {
const { t } = useTempoUi();
return (
<>
<Head title={t('Criar conta')} />
<Form
{...store.form()}
resetOnSuccess={['password', 'password_confirmation']}
disableWhileProcessing
className="flex flex-col gap-6"
>
{({ processing, errors }) => (
<AuthTempoCard
badge={t('Bolsa de Parcerias, Investidores e Capital')}
title={t('Criar conta')}
description={t(
'Crie uma conta simples para continuar e acompanhar a sua operação. Não é necessário configurar MFA para participar.',
)}
footer={
<>
<div className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="name">{t('Nome')}</Label>
{t('Já tem conta?')}{' '}
<TextLink
href={login()}
tabIndex={6}
className="font-bold text-[#ed1c24] no-underline hover:underline"
>
{t('Entrar')}
</TextLink>
</>
}
>
<Form
{...store.form()}
resetOnSuccess={['password', 'password_confirmation']}
disableWhileProcessing
className="flex flex-col gap-5"
>
{({ processing, errors }) => (
<div className="grid gap-5">
<div className="grid gap-1.5">
<Label htmlFor="name" className={labelClass}>
{t('Nome')}
</Label>
<Input
id="name"
defaultValue={participationIdentity().name}
@@ -40,15 +67,15 @@ export default function Register({ passwordRules }: Props) {
autoComplete="name"
name="name"
placeholder={t('Nome completo')}
className={controlClass}
/>
<InputError
message={errors.name}
className="mt-2"
/>
<InputError message={errors.name} />
</div>
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<div className="grid gap-1.5">
<Label htmlFor="email" className={labelClass}>
Email
</Label>
<Input
id="email"
defaultValue={participationIdentity().email}
@@ -58,12 +85,18 @@ export default function Register({ passwordRules }: Props) {
autoComplete="email"
name="email"
placeholder="email@example.com"
className={controlClass}
/>
<InputError message={errors.email} />
</div>
<div className="grid gap-2">
<Label htmlFor="password">{t('Palavra-passe')}</Label>
<div className="grid gap-1.5">
<Label
htmlFor="password"
className={labelClass}
>
{t('Palavra-passe')}
</Label>
<PasswordInput
id="password"
required
@@ -72,12 +105,16 @@ export default function Register({ passwordRules }: Props) {
name="password"
placeholder={t('Palavra-passe')}
passwordrules={passwordRules}
className={`${controlClass} pr-10`}
/>
<InputError message={errors.password} />
</div>
<div className="grid gap-2">
<Label htmlFor="password_confirmation">
<div className="grid gap-1.5">
<Label
htmlFor="password_confirmation"
className={labelClass}
>
{t('Confirmar palavra-passe')}
</Label>
<PasswordInput
@@ -88,6 +125,7 @@ export default function Register({ passwordRules }: Props) {
name="password_confirmation"
placeholder={t('Confirmar palavra-passe')}
passwordrules={passwordRules}
className={`${controlClass} pr-10`}
/>
<InputError
message={errors.password_confirmation}
@@ -96,29 +134,27 @@ export default function Register({ passwordRules }: Props) {
<Button
type="submit"
className="mt-2 w-full"
className="mt-1 flex w-full items-center justify-center gap-2 rounded-xl bg-[#ed1c24] px-6 py-4 font-bold text-white shadow-lg shadow-red-900/15 transition hover:-translate-y-0.5 hover:bg-[#c6161d] hover:shadow-xl disabled:translate-y-0 disabled:cursor-not-allowed disabled:opacity-60"
tabIndex={5}
data-test="register-user-button"
>
{processing && <Spinner />}
{processing ? (
<Spinner />
) : (
<UserPlus size={18} />
)}
{t('Criar conta')}
</Button>
</div>
<div className="text-muted-foreground text-center text-sm">
{t('Já tem conta?')}{' '}
<TextLink href={login()} tabIndex={6}>
{t('Entrar')}
</TextLink>
</div>
</>
)}
</Form>
)}
</Form>
</AuthTempoCard>
</>
);
}
Register.layout = {
variant: 'tempo-card',
title: 'Criar conta',
description:
'Crie uma conta simples para continuar e acompanhar a sua operação. Não é necessário configurar MFA para participar.',