mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🎨 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:
parent
80bc49db8d
commit
37c94beeac
10 changed files with 284 additions and 140 deletions
|
|
@ -22,17 +22,71 @@
|
|||
<!--<![endif]-->
|
||||
<title></title>
|
||||
<style type='text/css'>
|
||||
@media (prefers-color-scheme: dark) { .darkmode { background-color: #212121 !important; }
|
||||
.darkmode p { color: #ffffff !important; } } @media only screen and (min-width: 520px) {
|
||||
.u-row { 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; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.darkmode {
|
||||
background-color: #212121 !important;
|
||||
}
|
||||
.darkmode p {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 520px) {
|
||||
.u-row {
|
||||
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>
|
||||
</head>
|
||||
|
||||
|
|
|
|||
|
|
@ -22,18 +22,78 @@
|
|||
<!--<![endif]-->
|
||||
<title></title>
|
||||
<style type='text/css'>
|
||||
@media (prefers-color-scheme: dark) { .darkmode { background-color: #212121 !important; }
|
||||
.darkmode p { color: #ffffff !important; } } @media only screen and (min-width: 520px) {
|
||||
.u-row { 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; } 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; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.darkmode {
|
||||
background-color: #212121 !important;
|
||||
}
|
||||
.darkmode p {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 520px) {
|
||||
.u-row {
|
||||
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;
|
||||
}
|
||||
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>
|
||||
</head>
|
||||
|
||||
|
|
|
|||
|
|
@ -22,18 +22,75 @@
|
|||
<!--<![endif]-->
|
||||
<title></title>
|
||||
<style type='text/css'>
|
||||
@media (prefers-color-scheme: dark) { .darkmode { background-color: #212121 !important; }
|
||||
.darkmode p { color: #ffffff !important; } } @media only screen and (min-width: 520px) {
|
||||
.u-row { 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; } #u_body a { color: #0000ee;
|
||||
text-decoration: underline; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.darkmode {
|
||||
background-color: #212121 !important;
|
||||
}
|
||||
.darkmode p {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 520px) {
|
||||
.u-row {
|
||||
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;
|
||||
}
|
||||
#u_body a {
|
||||
color: #0000ee;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,12 @@
|
|||
import { TranslationKeys, useLocalize } from '~/hooks';
|
||||
import { BlinkAnimation } from './BlinkAnimation';
|
||||
import { TStartupConfig } from 'librechat-data-provider';
|
||||
import { ErrorMessage } from '~/components/Auth/ErrorMessage';
|
||||
import SocialLoginRender from './SocialLoginRender';
|
||||
import { ThemeSelector } from '~/components/ui';
|
||||
import { BlinkAnimation } from './BlinkAnimation';
|
||||
import { ThemeSelector } from '~/components';
|
||||
import { Banner } from '../Banners';
|
||||
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({
|
||||
children,
|
||||
header,
|
||||
|
|
@ -40,19 +29,29 @@ function AuthLayout({
|
|||
const hasStartupConfigError = startupConfigError !== null && startupConfigError !== undefined;
|
||||
const DisplayError = () => {
|
||||
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') {
|
||||
return (
|
||||
<ErrorRender>
|
||||
{localize('com_auth_error_invalid_reset_token')}{' '}
|
||||
<a className="font-semibold text-green-600 hover:underline" href="/forgot-password">
|
||||
{localize('com_auth_click_here')}
|
||||
</a>{' '}
|
||||
{localize('com_auth_to_try_again')}
|
||||
</ErrorRender>
|
||||
<div className="mx-auto sm:max-w-sm">
|
||||
<ErrorMessage>
|
||||
{localize('com_auth_error_invalid_reset_token')}{' '}
|
||||
<a className="font-semibold text-green-600 hover:underline" href="/forgot-password">
|
||||
{localize('com_auth_click_here')}
|
||||
</a>{' '}
|
||||
{localize('com_auth_to_try_again')}
|
||||
</ErrorMessage>
|
||||
</div>
|
||||
);
|
||||
} 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;
|
||||
};
|
||||
|
|
@ -87,8 +86,8 @@ function AuthLayout({
|
|||
{children}
|
||||
{!pathname.includes('2fa') &&
|
||||
(pathname.includes('login') || pathname.includes('register')) && (
|
||||
<SocialLoginRender startupConfig={startupConfig} />
|
||||
)}
|
||||
<SocialLoginRender startupConfig={startupConfig} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Footer startupConfig={startupConfig} />
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ export const ErrorMessage = ({ children }: { children: React.ReactNode }) => (
|
|||
<div
|
||||
role="alert"
|
||||
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}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import { useEffect, useState } from 'react';
|
|||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import type { TLoginLayoutContext } from '~/common';
|
||||
import { ErrorMessage } from '~/components/Auth/ErrorMessage';
|
||||
import SocialButton from '~/components/Auth/SocialButton';
|
||||
import { OpenIDIcon } from '~/components';
|
||||
import { getLoginError } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import LoginForm from './LoginForm';
|
||||
import SocialButton from '~/components/Auth/SocialButton';
|
||||
import { OpenIDIcon } from '~/components';
|
||||
|
||||
function Login() {
|
||||
const localize = useLocalize();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type { TLoginUser, TStartupConfig } from 'librechat-data-provider';
|
|||
import type { TAuthContext } from '~/common';
|
||||
import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider';
|
||||
import { ThemeContext, useLocalize } from '~/hooks';
|
||||
import { Spinner, Button } from '~/components';
|
||||
|
||||
type TLoginFormProps = {
|
||||
onSubmit: (data: TLoginUser) => void;
|
||||
|
|
@ -20,7 +21,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
|||
register,
|
||||
getValues,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TLoginUser>();
|
||||
const [showResendLink, setShowResendLink] = useState<boolean>(false);
|
||||
const [turnstileToken, setTurnstileToken] = useState<string | null>(null);
|
||||
|
|
@ -165,15 +166,16 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
|||
)}
|
||||
|
||||
<div className="mt-6">
|
||||
<button
|
||||
<Button
|
||||
aria-label={localize('com_auth_continue')}
|
||||
data-testid="login-button"
|
||||
type="submit"
|
||||
disabled={requireCaptcha && !turnstileToken}
|
||||
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"
|
||||
disabled={(requireCaptcha && !turnstileToken) || isSubmitting}
|
||||
variant="submit"
|
||||
className="h-12 w-full rounded-2xl"
|
||||
>
|
||||
{localize('com_auth_continue')}
|
||||
</button>
|
||||
{isSubmitting ? <Spinner /> : localize('com_auth_continue')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import { Turnstile } from '@marsidev/react-turnstile';
|
|||
import { useNavigate, useOutletContext, useLocation } from 'react-router-dom';
|
||||
import { useRegisterUserMutation } from 'librechat-data-provider/react-query';
|
||||
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 type { TLoginLayoutContext } from '~/common';
|
||||
import { Spinner, Button } from '~/components';
|
||||
import { ErrorMessage } from './ErrorMessage';
|
||||
|
||||
const Registration: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -194,7 +194,7 @@ const Registration: React.FC = () => {
|
|||
)}
|
||||
|
||||
<div className="mt-6">
|
||||
<button
|
||||
<Button
|
||||
disabled={
|
||||
Object.keys(errors).length > 0 ||
|
||||
isSubmitting ||
|
||||
|
|
@ -202,10 +202,11 @@ const Registration: React.FC = () => {
|
|||
}
|
||||
type="submit"
|
||||
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')}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ import { useRequestPasswordResetMutation } from 'librechat-data-provider/react-q
|
|||
import type { TRequestPasswordReset, TRequestPasswordResetResponse } from 'librechat-data-provider';
|
||||
import type { FC } from 'react';
|
||||
import type { TLoginLayoutContext } from '~/common';
|
||||
import { Spinner, Button } from '~/components';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const BodyTextWrapper: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
{children}
|
||||
|
|
@ -44,6 +45,7 @@ function RequestPasswordReset() {
|
|||
const { startupConfig, setHeaderText } = useOutletContext<TLoginLayoutContext>();
|
||||
|
||||
const requestPasswordReset = useRequestPasswordResetMutation();
|
||||
const { isLoading } = requestPasswordReset;
|
||||
|
||||
const onSubmit = (data: TRequestPasswordReset) => {
|
||||
requestPasswordReset.mutate(data, {
|
||||
|
|
@ -105,23 +107,12 @@ function RequestPasswordReset() {
|
|||
},
|
||||
})}
|
||||
aria-invalid={!!errors.email}
|
||||
className="
|
||||
peer w-full rounded-lg border border-gray-300 bg-transparent px-4 py-3
|
||||
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"
|
||||
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"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
htmlFor="email"
|
||||
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
|
||||
"
|
||||
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"
|
||||
>
|
||||
{localize('com_auth_email_address')}
|
||||
</label>
|
||||
|
|
@ -133,18 +124,15 @@ function RequestPasswordReset() {
|
|||
)}
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<button
|
||||
<Button
|
||||
aria-label="Continue with password reset"
|
||||
type="submit"
|
||||
disabled={!!errors.email}
|
||||
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
|
||||
"
|
||||
disabled={!!errors.email || isLoading}
|
||||
variant="submit"
|
||||
className="h-12 w-full rounded-2xl"
|
||||
>
|
||||
{localize('com_auth_continue')}
|
||||
</button>
|
||||
{isLoading ? <Spinner /> : localize('com_auth_continue')}
|
||||
</Button>
|
||||
<a
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
|||
import { useResetPasswordMutation } from 'librechat-data-provider/react-query';
|
||||
import type { TResetPassword } from 'librechat-data-provider';
|
||||
import type { TLoginLayoutContext } from '~/common';
|
||||
import { Spinner, Button } from '~/components';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
function ResetPassword() {
|
||||
|
|
@ -12,7 +13,7 @@ function ResetPassword() {
|
|||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TResetPassword>();
|
||||
const navigate = useNavigate();
|
||||
const [params] = useSearchParams();
|
||||
|
|
@ -35,18 +36,20 @@ function ResetPassword() {
|
|||
return (
|
||||
<>
|
||||
<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"
|
||||
>
|
||||
{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>
|
||||
<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}
|
||||
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
|
||||
"
|
||||
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"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
htmlFor="password"
|
||||
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
|
||||
"
|
||||
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"
|
||||
>
|
||||
{localize('com_auth_password')}
|
||||
</label>
|
||||
|
|
@ -124,20 +119,12 @@ function ResetPassword() {
|
|||
validate: (value) => value === password || localize('com_auth_password_not_match'),
|
||||
})}
|
||||
aria-invalid={!!errors.confirm_password}
|
||||
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
|
||||
"
|
||||
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"
|
||||
placeholder=" "
|
||||
/>
|
||||
<label
|
||||
htmlFor="confirm_password"
|
||||
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
|
||||
"
|
||||
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"
|
||||
>
|
||||
{localize('com_auth_password_confirm')}
|
||||
</label>
|
||||
|
|
@ -159,19 +146,15 @@ function ResetPassword() {
|
|||
)}
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<button
|
||||
disabled={!!errors.password || !!errors.confirm_password}
|
||||
<Button
|
||||
type="submit"
|
||||
aria-label={localize('com_auth_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
|
||||
"
|
||||
disabled={!!errors.password || !!errors.confirm_password || isSubmitting}
|
||||
variant="submit"
|
||||
className="h-12 w-full rounded-2xl"
|
||||
>
|
||||
{localize('com_auth_continue')}
|
||||
</button>
|
||||
{isSubmitting ? <Spinner /> : localize('com_auth_continue')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue