🎨 refactor: Auth Components UI Consistency (#7651)

* 🔧 refactor: Improve Error Handling and UI Consistency in Auth Components

* 🔧 refactor: Email Templates

* 🔧 refactor: Enhance LoginForm with loading state and spinner

* 🔧 refactor: Replace button elements with Button component and enhance UI consistency across Auth forms
This commit is contained in:
Marco Beretta 2025-06-02 13:49:10 +02:00 committed by GitHub
parent 80bc49db8d
commit 37c94beeac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 284 additions and 140 deletions

View file

@ -22,17 +22,71 @@
<!--<![endif]--> <!--<![endif]-->
<title></title> <title></title>
<style type='text/css'> <style type='text/css'>
@media (prefers-color-scheme: dark) { .darkmode { background-color: #212121 !important; } @media (prefers-color-scheme: dark) {
.darkmode p { color: #ffffff !important; } } @media only screen and (min-width: 520px) { .darkmode {
.u-row { width: 500px !important; } .u-row .u-col { vertical-align: top; } .u-row .u-col-100 { background-color: #212121 !important;
width: 500px !important; } } @media (max-width: 520px) { .u-row-container { max-width: 100% }
!important; padding-left: 0px !important; padding-right: 0px !important; } .u-row .u-col { .darkmode p {
min-width: 320px !important; max-width: 100% !important; display: block !important; } .u-row { color: #ffffff !important;
width: 100% !important; } .u-col { width: 100% !important; } .u-col>div { margin: 0 auto; } } }
body { margin: 0; padding: 0; } table, tr, td { vertical-align: top; border-collapse: }
collapse; } .ie-container table, .mso-container table { table-layout: fixed; } * { @media only screen and (min-width: 520px) {
line-height: inherit; } a[x-apple-data-detectors='true'] { color: inherit !important; .u-row {
text-decoration: none !important; } table, td { color: #ffffff; } width: 500px !important;
}
.u-row .u-col {
vertical-align: top;
}
.u-row .u-col-100 {
width: 500px !important;
}
}
@media (max-width: 520px) {
.u-row-container {
max-width: 100% !important;
padding-left: 0px !important;
padding-right: 0px !important;
}
.u-row .u-col {
min-width: 320px !important;
max-width: 100% !important;
display: block !important;
}
.u-row {
width: 100% !important;
}
.u-col {
width: 100% !important;
}
.u-col > div {
margin: 0 auto;
}
}
body {
margin: 0;
padding: 0;
}
table,
tr,
td {
vertical-align: top;
border-collapse: collapse;
}
.ie-container table,
.mso-container table {
table-layout: fixed;
}
* {
line-height: inherit;
}
a[x-apple-data-detectors='true'] {
color: inherit !important;
text-decoration: none !important;
}
table,
td {
color: #ffffff;
}
</style> </style>
</head> </head>

View file

@ -22,18 +22,78 @@
<!--<![endif]--> <!--<![endif]-->
<title></title> <title></title>
<style type='text/css'> <style type='text/css'>
@media (prefers-color-scheme: dark) { .darkmode { background-color: #212121 !important; } @media (prefers-color-scheme: dark) {
.darkmode p { color: #ffffff !important; } } @media only screen and (min-width: 520px) { .darkmode {
.u-row { width: 500px !important; } .u-row .u-col { vertical-align: top; } .u-row .u-col-100 { background-color: #212121 !important;
width: 500px !important; } } @media (max-width: 520px) { .u-row-container { max-width: 100% }
!important; padding-left: 0px !important; padding-right: 0px !important; } .u-row .u-col { .darkmode p {
min-width: 320px !important; max-width: 100% !important; display: block !important; } .u-row { color: #ffffff !important;
width: 100% !important; } .u-col { width: 100% !important; } .u-col>div { margin: 0 auto; } } }
body { margin: 0; padding: 0; } table, tr, td { vertical-align: top; border-collapse: }
collapse; } p { margin: 0; } .ie-container table, .mso-container table { table-layout: fixed; @media only screen and (min-width: 520px) {
} * { line-height: inherit; } a[x-apple-data-detectors='true'] { color: inherit !important; .u-row {
text-decoration: none !important; } table, td { color: #ffffff; } #u_body a { color: #0000ee; width: 500px !important;
text-decoration: underline; } }
.u-row .u-col {
vertical-align: top;
}
.u-row .u-col-100 {
width: 500px !important;
}
}
@media (max-width: 520px) {
.u-row-container {
max-width: 100% !important;
padding-left: 0px !important;
padding-right: 0px !important;
}
.u-row .u-col {
min-width: 320px !important;
max-width: 100% !important;
display: block !important;
}
.u-row {
width: 100% !important;
}
.u-col {
width: 100% !important;
}
.u-col > div {
margin: 0 auto;
}
}
body {
margin: 0;
padding: 0;
}
table,
tr,
td {
vertical-align: top;
border-collapse: collapse;
}
p {
margin: 0;
}
.ie-container table,
.mso-container table {
table-layout: fixed;
}
* {
line-height: inherit;
}
a[x-apple-data-detectors='true'] {
color: inherit !important;
text-decoration: none !important;
}
table,
td {
color: #ffffff;
}
#u_body a {
color: #0000ee;
text-decoration: underline;
}
</style> </style>
</head> </head>

View file

@ -22,18 +22,75 @@
<!--<![endif]--> <!--<![endif]-->
<title></title> <title></title>
<style type='text/css'> <style type='text/css'>
@media (prefers-color-scheme: dark) { .darkmode { background-color: #212121 !important; } @media (prefers-color-scheme: dark) {
.darkmode p { color: #ffffff !important; } } @media only screen and (min-width: 520px) { .darkmode {
.u-row { width: 500px !important; } .u-row .u-col { vertical-align: top; } .u-row .u-col-100 { background-color: #212121 !important;
width: 500px !important; } } @media (max-width: 520px) { .u-row-container { max-width: 100% }
!important; padding-left: 0px !important; padding-right: 0px !important; } .u-row .u-col { .darkmode p {
min-width: 320px !important; max-width: 100% !important; display: block !important; } .u-row { color: #ffffff !important;
width: 100% !important; } .u-col { width: 100% !important; } .u-col>div { margin: 0 auto; } } }
body { margin: 0; padding: 0; } table, tr, td { vertical-align: top; border-collapse: }
collapse; } .ie-container table, .mso-container table { table-layout: fixed; } * { @media only screen and (min-width: 520px) {
line-height: inherit; } a[x-apple-data-detectors='true'] { color: inherit !important; .u-row {
text-decoration: none !important; } table, td { color: #ffffff; } #u_body a { color: #0000ee; width: 500px !important;
text-decoration: underline; } }
.u-row .u-col {
vertical-align: top;
}
.u-row .u-col-100 {
width: 500px !important;
}
}
@media (max-width: 520px) {
.u-row-container {
max-width: 100% !important;
padding-left: 0px !important;
padding-right: 0px !important;
}
.u-row .u-col {
min-width: 320px !important;
max-width: 100% !important;
display: block !important;
}
.u-row {
width: 100% !important;
}
.u-col {
width: 100% !important;
}
.u-col > div {
margin: 0 auto;
}
}
body {
margin: 0;
padding: 0;
}
table,
tr,
td {
vertical-align: top;
border-collapse: collapse;
}
.ie-container table,
.mso-container table {
table-layout: fixed;
}
* {
line-height: inherit;
}
a[x-apple-data-detectors='true'] {
color: inherit !important;
text-decoration: none !important;
}
table,
td {
color: #ffffff;
}
#u_body a {
color: #0000ee;
text-decoration: underline;
}
</style> </style>
</head> </head>

View file

@ -1,23 +1,12 @@
import { TranslationKeys, useLocalize } from '~/hooks'; import { TranslationKeys, useLocalize } from '~/hooks';
import { BlinkAnimation } from './BlinkAnimation';
import { TStartupConfig } from 'librechat-data-provider'; import { TStartupConfig } from 'librechat-data-provider';
import { ErrorMessage } from '~/components/Auth/ErrorMessage';
import SocialLoginRender from './SocialLoginRender'; import SocialLoginRender from './SocialLoginRender';
import { ThemeSelector } from '~/components/ui'; import { BlinkAnimation } from './BlinkAnimation';
import { ThemeSelector } from '~/components';
import { Banner } from '../Banners'; import { Banner } from '../Banners';
import Footer from './Footer'; import Footer from './Footer';
const ErrorRender = ({ children }: { children: React.ReactNode }) => (
<div className="mt-16 flex justify-center">
<div
role="alert"
aria-live="assertive"
className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200"
>
{children}
</div>
</div>
);
function AuthLayout({ function AuthLayout({
children, children,
header, header,
@ -40,19 +29,29 @@ function AuthLayout({
const hasStartupConfigError = startupConfigError !== null && startupConfigError !== undefined; const hasStartupConfigError = startupConfigError !== null && startupConfigError !== undefined;
const DisplayError = () => { const DisplayError = () => {
if (hasStartupConfigError) { if (hasStartupConfigError) {
return <ErrorRender>{localize('com_auth_error_login_server')}</ErrorRender>; return (
<div className="mx-auto sm:max-w-sm">
<ErrorMessage>{localize('com_auth_error_login_server')}</ErrorMessage>
</div>
);
} else if (error === 'com_auth_error_invalid_reset_token') { } else if (error === 'com_auth_error_invalid_reset_token') {
return ( return (
<ErrorRender> <div className="mx-auto sm:max-w-sm">
{localize('com_auth_error_invalid_reset_token')}{' '} <ErrorMessage>
<a className="font-semibold text-green-600 hover:underline" href="/forgot-password"> {localize('com_auth_error_invalid_reset_token')}{' '}
{localize('com_auth_click_here')} <a className="font-semibold text-green-600 hover:underline" href="/forgot-password">
</a>{' '} {localize('com_auth_click_here')}
{localize('com_auth_to_try_again')} </a>{' '}
</ErrorRender> {localize('com_auth_to_try_again')}
</ErrorMessage>
</div>
); );
} else if (error != null && error) { } else if (error != null && error) {
return <ErrorRender>{localize(error)}</ErrorRender>; return (
<div className="mx-auto sm:max-w-sm">
<ErrorMessage>{localize(error)}</ErrorMessage>
</div>
);
} }
return null; return null;
}; };
@ -87,8 +86,8 @@ function AuthLayout({
{children} {children}
{!pathname.includes('2fa') && {!pathname.includes('2fa') &&
(pathname.includes('login') || pathname.includes('register')) && ( (pathname.includes('login') || pathname.includes('register')) && (
<SocialLoginRender startupConfig={startupConfig} /> <SocialLoginRender startupConfig={startupConfig} />
)} )}
</div> </div>
</div> </div>
<Footer startupConfig={startupConfig} /> <Footer startupConfig={startupConfig} />

View file

@ -2,7 +2,7 @@ export const ErrorMessage = ({ children }: { children: React.ReactNode }) => (
<div <div
role="alert" role="alert"
aria-live="assertive" aria-live="assertive"
className="relative mt-6 rounded-lg border border-red-500/20 bg-red-50/50 px-6 py-4 text-red-700 shadow-sm transition-all dark:bg-red-950/30 dark:text-red-100" className="relative mt-6 rounded-xl border border-red-500/20 bg-red-50/50 px-6 py-4 text-red-700 shadow-sm transition-all dark:bg-red-950/30 dark:text-red-100"
> >
{children} {children}
</div> </div>

View file

@ -3,11 +3,11 @@ import { useEffect, useState } from 'react';
import { useAuthContext } from '~/hooks/AuthContext'; import { useAuthContext } from '~/hooks/AuthContext';
import type { TLoginLayoutContext } from '~/common'; import type { TLoginLayoutContext } from '~/common';
import { ErrorMessage } from '~/components/Auth/ErrorMessage'; import { ErrorMessage } from '~/components/Auth/ErrorMessage';
import SocialButton from '~/components/Auth/SocialButton';
import { OpenIDIcon } from '~/components';
import { getLoginError } from '~/utils'; import { getLoginError } from '~/utils';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import LoginForm from './LoginForm'; import LoginForm from './LoginForm';
import SocialButton from '~/components/Auth/SocialButton';
import { OpenIDIcon } from '~/components';
function Login() { function Login() {
const localize = useLocalize(); const localize = useLocalize();

View file

@ -5,6 +5,7 @@ import type { TLoginUser, TStartupConfig } from 'librechat-data-provider';
import type { TAuthContext } from '~/common'; import type { TAuthContext } from '~/common';
import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider'; import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider';
import { ThemeContext, useLocalize } from '~/hooks'; import { ThemeContext, useLocalize } from '~/hooks';
import { Spinner, Button } from '~/components';
type TLoginFormProps = { type TLoginFormProps = {
onSubmit: (data: TLoginUser) => void; onSubmit: (data: TLoginUser) => void;
@ -20,7 +21,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
register, register,
getValues, getValues,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors, isSubmitting },
} = useForm<TLoginUser>(); } = useForm<TLoginUser>();
const [showResendLink, setShowResendLink] = useState<boolean>(false); const [showResendLink, setShowResendLink] = useState<boolean>(false);
const [turnstileToken, setTurnstileToken] = useState<string | null>(null); const [turnstileToken, setTurnstileToken] = useState<string | null>(null);
@ -165,15 +166,16 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
)} )}
<div className="mt-6"> <div className="mt-6">
<button <Button
aria-label={localize('com_auth_continue')} aria-label={localize('com_auth_continue')}
data-testid="login-button" data-testid="login-button"
type="submit" type="submit"
disabled={requireCaptcha && !turnstileToken} disabled={(requireCaptcha && !turnstileToken) || isSubmitting}
className="w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white transition-colors hover:bg-green-700 disabled:opacity-50 disabled:hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700" variant="submit"
className="h-12 w-full rounded-2xl"
> >
{localize('com_auth_continue')} {isSubmitting ? <Spinner /> : localize('com_auth_continue')}
</button> </Button>
</div> </div>
</form> </form>
</> </>

View file

@ -4,10 +4,10 @@ import { Turnstile } from '@marsidev/react-turnstile';
import { useNavigate, useOutletContext, useLocation } from 'react-router-dom'; import { useNavigate, useOutletContext, useLocation } from 'react-router-dom';
import { useRegisterUserMutation } from 'librechat-data-provider/react-query'; import { useRegisterUserMutation } from 'librechat-data-provider/react-query';
import type { TRegisterUser, TError } from 'librechat-data-provider'; import type { TRegisterUser, TError } from 'librechat-data-provider';
import type { TLoginLayoutContext } from '~/common';
import { ErrorMessage } from './ErrorMessage';
import { Spinner } from '~/components/svg';
import { useLocalize, TranslationKeys, ThemeContext } from '~/hooks'; import { useLocalize, TranslationKeys, ThemeContext } from '~/hooks';
import type { TLoginLayoutContext } from '~/common';
import { Spinner, Button } from '~/components';
import { ErrorMessage } from './ErrorMessage';
const Registration: React.FC = () => { const Registration: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -194,7 +194,7 @@ const Registration: React.FC = () => {
)} )}
<div className="mt-6"> <div className="mt-6">
<button <Button
disabled={ disabled={
Object.keys(errors).length > 0 || Object.keys(errors).length > 0 ||
isSubmitting || isSubmitting ||
@ -202,10 +202,11 @@ const Registration: React.FC = () => {
} }
type="submit" type="submit"
aria-label="Submit registration" aria-label="Submit registration"
className="w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white transition-colors hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50 disabled:hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700" variant="submit"
className="h-12 w-full rounded-2xl"
> >
{isSubmitting ? <Spinner /> : localize('com_auth_continue')} {isSubmitting ? <Spinner /> : localize('com_auth_continue')}
</button> </Button>
</div> </div>
</form> </form>

View file

@ -5,12 +5,13 @@ import { useRequestPasswordResetMutation } from 'librechat-data-provider/react-q
import type { TRequestPasswordReset, TRequestPasswordResetResponse } from 'librechat-data-provider'; import type { TRequestPasswordReset, TRequestPasswordResetResponse } from 'librechat-data-provider';
import type { FC } from 'react'; import type { FC } from 'react';
import type { TLoginLayoutContext } from '~/common'; import type { TLoginLayoutContext } from '~/common';
import { Spinner, Button } from '~/components';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
const BodyTextWrapper: FC<{ children: ReactNode }> = ({ children }) => { const BodyTextWrapper: FC<{ children: ReactNode }> = ({ children }) => {
return ( return (
<div <div
className="relative mt-6 rounded-lg border border-green-500/20 bg-green-50/50 px-6 py-4 text-green-700 shadow-sm transition-all dark:bg-green-950/30 dark:text-green-100" className="relative mt-6 rounded-xl border border-green-500/20 bg-green-50/50 px-6 py-4 text-green-700 shadow-sm transition-all dark:bg-green-950/30 dark:text-green-100"
role="alert" role="alert"
> >
{children} {children}
@ -44,6 +45,7 @@ function RequestPasswordReset() {
const { startupConfig, setHeaderText } = useOutletContext<TLoginLayoutContext>(); const { startupConfig, setHeaderText } = useOutletContext<TLoginLayoutContext>();
const requestPasswordReset = useRequestPasswordResetMutation(); const requestPasswordReset = useRequestPasswordResetMutation();
const { isLoading } = requestPasswordReset;
const onSubmit = (data: TRequestPasswordReset) => { const onSubmit = (data: TRequestPasswordReset) => {
requestPasswordReset.mutate(data, { requestPasswordReset.mutate(data, {
@ -105,23 +107,12 @@ function RequestPasswordReset() {
}, },
})} })}
aria-invalid={!!errors.email} aria-invalid={!!errors.email}
className=" className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
peer w-full rounded-lg border border-gray-300 bg-transparent px-4 py-3 placeholder=" "
text-base text-gray-900 placeholder-transparent transition-all
focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20
dark:border-gray-700 dark:text-white dark:focus:border-green-500
"
placeholder="email@example.com"
/> />
<label <label
htmlFor="email" htmlFor="email"
className=" className="absolute -top-2 left-2 z-10 bg-white px-2 text-sm text-gray-600 transition-all peer-placeholder-shown:top-3 peer-placeholder-shown:text-base peer-placeholder-shown:text-gray-500 peer-focus:-top-2 peer-focus:text-sm peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500"
absolute -top-2 left-2 z-10 bg-white px-2 text-sm text-gray-600
transition-all peer-placeholder-shown:top-3 peer-placeholder-shown:text-base
peer-placeholder-shown:text-gray-500 peer-focus:-top-2 peer-focus:text-sm
peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400
dark:peer-focus:text-green-500
"
> >
{localize('com_auth_email_address')} {localize('com_auth_email_address')}
</label> </label>
@ -133,18 +124,15 @@ function RequestPasswordReset() {
)} )}
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<button <Button
aria-label="Continue with password reset"
type="submit" type="submit"
disabled={!!errors.email} disabled={!!errors.email || isLoading}
className=" variant="submit"
w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white className="h-12 w-full rounded-2xl"
transition-colors hover:bg-green-700 focus:outline-none focus:ring-2
focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50
disabled:hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700
"
> >
{localize('com_auth_continue')} {isLoading ? <Spinner /> : localize('com_auth_continue')}
</button> </Button>
<a <a
href="/login" href="/login"
className="block text-center text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300" className="block text-center text-sm font-medium text-green-600 transition-colors hover:text-green-700 dark:text-green-400 dark:hover:text-green-300"

View file

@ -4,6 +4,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
import { useResetPasswordMutation } from 'librechat-data-provider/react-query'; import { useResetPasswordMutation } from 'librechat-data-provider/react-query';
import type { TResetPassword } from 'librechat-data-provider'; import type { TResetPassword } from 'librechat-data-provider';
import type { TLoginLayoutContext } from '~/common'; import type { TLoginLayoutContext } from '~/common';
import { Spinner, Button } from '~/components';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
function ResetPassword() { function ResetPassword() {
@ -12,7 +13,7 @@ function ResetPassword() {
register, register,
handleSubmit, handleSubmit,
watch, watch,
formState: { errors }, formState: { errors, isSubmitting },
} = useForm<TResetPassword>(); } = useForm<TResetPassword>();
const navigate = useNavigate(); const navigate = useNavigate();
const [params] = useSearchParams(); const [params] = useSearchParams();
@ -35,18 +36,20 @@ function ResetPassword() {
return ( return (
<> <>
<div <div
className="relative mb-8 mt-4 rounded-2xl border border-green-400 bg-green-100 px-4 py-3 text-center text-green-700 dark:bg-gray-900 dark:text-white" className="relative mt-6 rounded-xl border border-green-500/20 bg-green-50/50 px-6 py-4 text-green-700 shadow-sm transition-all dark:bg-green-950/30 dark:text-green-100"
role="alert" role="alert"
> >
{localize('com_auth_login_with_new_password')} <div className="flex flex-col space-y-4">
<p>{localize('com_auth_login_with_new_password')}</p>
<Button
onClick={() => navigate('/login')}
aria-label={localize('com_auth_sign_in')}
variant="submit"
>
{localize('com_auth_continue')}
</Button>
</div>
</div> </div>
<button
onClick={() => navigate('/login')}
aria-label={localize('com_auth_sign_in')}
className="w-full transform rounded-2xl bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-600 focus:bg-green-600 focus:outline-none"
>
{localize('com_auth_continue')}
</button>
</> </>
); );
} }
@ -89,20 +92,12 @@ function ResetPassword() {
}, },
})} })}
aria-invalid={!!errors.password} aria-invalid={!!errors.password}
className=" className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light
bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none
"
placeholder=" " placeholder=" "
/> />
<label <label
htmlFor="password" htmlFor="password"
className=" className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
"
> >
{localize('com_auth_password')} {localize('com_auth_password')}
</label> </label>
@ -124,20 +119,12 @@ function ResetPassword() {
validate: (value) => value === password || localize('com_auth_password_not_match'), validate: (value) => value === password || localize('com_auth_password_not_match'),
})} })}
aria-invalid={!!errors.confirm_password} aria-invalid={!!errors.confirm_password}
className=" className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light
bg-surface-primary px-3.5 pb-2.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none
"
placeholder=" " placeholder=" "
/> />
<label <label
htmlFor="confirm_password" htmlFor="confirm_password"
className=" className="absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
"
> >
{localize('com_auth_password_confirm')} {localize('com_auth_password_confirm')}
</label> </label>
@ -159,19 +146,15 @@ function ResetPassword() {
)} )}
</div> </div>
<div className="mt-6"> <div className="mt-6">
<button <Button
disabled={!!errors.password || !!errors.confirm_password}
type="submit" type="submit"
aria-label={localize('com_auth_submit_registration')} aria-label={localize('com_auth_submit_registration')}
className=" disabled={!!errors.password || !!errors.confirm_password || isSubmitting}
w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white variant="submit"
transition-colors hover:bg-green-700 focus:outline-none focus:ring-2 className="h-12 w-full rounded-2xl"
focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50
disabled:hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-700
"
> >
{localize('com_auth_continue')} {isSubmitting ? <Spinner /> : localize('com_auth_continue')}
</button> </Button>
</div> </div>
</form> </form>
); );