This commit is contained in:
ahmedasad236 2026-04-05 01:45:17 +00:00 committed by GitHub
commit bf1bcf640b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 163 additions and 127 deletions

View file

@ -7,6 +7,7 @@ import type { TAuthContext } from '~/common';
import { useResendVerificationEmail, useGetStartupConfig } from '~/data-provider';
import { validateEmail } from '~/utils';
import { useLocalize } from '~/hooks';
import PasswordInput from './PasswordInput';
type TLoginFormProps = {
onSubmit: (data: TLoginUser) => void;
@ -116,34 +117,20 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
</div>
{renderError('email')}
</div>
<div className="mb-2">
<div className="relative">
<input
type="password"
id="password"
autoComplete="current-password"
aria-label={localize('com_auth_password')}
{...register('password', {
required: localize('com_auth_password_required'),
minLength: {
value: startupConfig?.minPasswordLength || 8,
message: localize('com_auth_password_min_length'),
},
maxLength: { value: 128, message: localize('com_auth_password_max_length') },
})}
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"
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-600 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
>
{localize('com_auth_password')}
</label>
</div>
{renderError('password')}
</div>
<PasswordInput
id="password"
label={localize('com_auth_password')}
autoComplete="current-password"
register={register('password', {
required: localize('com_auth_password_required'),
minLength: {
value: startupConfig?.minPasswordLength || 8,
message: localize('com_auth_password_min_length'),
},
maxLength: { value: 128, message: localize('com_auth_password_max_length') },
})}
error={errors.password}
/>
{startupConfig.passwordResetEnabled && (
<a
href="/forgot-password"

View file

@ -0,0 +1,68 @@
import React, { useState } from 'react';
import { Eye, EyeOff } from 'lucide-react';
import type { UseFormRegisterReturn, FieldError } from 'react-hook-form';
import { useLocalize } from '~/hooks';
interface PasswordInputProps {
id: string;
label: string;
register: UseFormRegisterReturn;
error?: FieldError;
autoComplete?: string;
'data-testid'?: string;
}
const PasswordInput: React.FC<PasswordInputProps> = ({
id,
label,
register,
error,
autoComplete = 'current-password',
'data-testid': dataTestId,
}) => {
const localize = useLocalize();
const [showPassword, setShowPassword] = useState(false);
const togglePasswordVisibility = () => {
setShowPassword((prev) => !prev);
};
return (
<div className="mb-2">
<div className="relative">
<input
type={showPassword ? 'text' : 'password'}
id={id}
autoComplete={autoComplete}
aria-label={label}
{...register}
aria-invalid={!!error}
className="webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light bg-surface-primary pb-2.5 pe-10 ps-3.5 pt-3 text-text-primary duration-200 focus:border-green-500 focus:outline-none"
placeholder=" "
data-testid={dataTestId}
/>
<label
htmlFor={id}
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-600 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
>
{label}
</label>
<button
type="button"
onClick={togglePasswordVisibility}
className="absolute end-3 top-1/2 -translate-y-1/2 text-text-secondary transition-colors hover:text-text-primary"
aria-label={localize(showPassword ? 'com_ui_hide_password' : 'com_ui_show_password')}
>
{showPassword ? <EyeOff className="size-5" /> : <Eye className="size-5" />}
</button>
</div>
{error && (
<span role="alert" className="mt-1 text-sm text-red-600 dark:text-red-500">
{String(error.message)}
</span>
)}
</div>
);
};
export default PasswordInput;

View file

@ -9,6 +9,7 @@ import type { TRegisterUser, TError } from 'librechat-data-provider';
import type { TLoginLayoutContext } from '~/common';
import { useLocalize, TranslationKeys } from '~/hooks';
import { ErrorMessage } from './ErrorMessage';
import PasswordInput from './PasswordInput';
const Registration: React.FC = () => {
const navigate = useNavigate();
@ -163,21 +164,35 @@ const Registration: React.FC = () => {
message: localize('com_auth_email_pattern'),
},
})}
{renderInput('password', 'com_auth_password', 'password', {
required: localize('com_auth_password_required'),
minLength: {
value: startupConfig?.minPasswordLength || 8,
message: localize('com_auth_password_min_length'),
},
maxLength: {
value: 128,
message: localize('com_auth_password_max_length'),
},
})}
{renderInput('confirm_password', 'com_auth_password_confirm', 'password', {
validate: (value: string) =>
value === password || localize('com_auth_password_not_match'),
})}
<PasswordInput
id="password"
label={localize('com_auth_password')}
autoComplete="new-password"
register={register('password', {
required: localize('com_auth_password_required'),
minLength: {
value: startupConfig?.minPasswordLength || 8,
message: localize('com_auth_password_min_length'),
},
maxLength: {
value: 128,
message: localize('com_auth_password_max_length'),
},
})}
error={errors.password}
data-testid="password"
/>
<PasswordInput
id="confirm_password"
label={localize('com_auth_password_confirm')}
autoComplete="new-password"
register={register('confirm_password', {
validate: (value) =>
value === password || localize('com_auth_password_not_match'),
})}
error={errors.confirm_password}
data-testid="confirm_password"
/>
{startupConfig?.turnstile?.siteKey && (
<div className="my-4 flex justify-center">

View file

@ -6,6 +6,7 @@ import { useResetPasswordMutation } from 'librechat-data-provider/react-query';
import type { TResetPassword } from 'librechat-data-provider';
import type { TLoginLayoutContext } from '~/common';
import { useLocalize } from '~/hooks';
import PasswordInput from './PasswordInput';
function ResetPassword() {
const localize = useLocalize();
@ -61,90 +62,54 @@ function ResetPassword() {
method="POST"
onSubmit={handleSubmit(onSubmit)}
>
<div className="mb-2">
<div className="relative">
<input
type="hidden"
id="token"
value={params.get('token') ?? ''}
{...register('token', { required: 'Unable to process: No valid reset token' })}
/>
<input
type="hidden"
id="userId"
value={params.get('userId') ?? ''}
{...register('userId', { required: 'Unable to process: No valid user id' })}
/>
<input
type="password"
id="password"
autoComplete="current-password"
aria-label={localize('com_auth_password')}
{...register('password', {
required: localize('com_auth_password_required'),
minLength: {
value: startupConfig?.minPasswordLength || 8,
message: localize('com_auth_password_min_length'),
},
maxLength: {
value: 128,
message: localize('com_auth_password_max_length'),
},
})}
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"
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"
>
{localize('com_auth_password')}
</label>
</div>
{errors.password && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{errors.password.message}
</span>
)}
</div>
<div className="mb-2">
<div className="relative">
<input
type="password"
id="confirm_password"
aria-label={localize('com_auth_password_confirm')}
{...register('confirm_password', {
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"
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"
>
{localize('com_auth_password_confirm')}
</label>
</div>
{errors.confirm_password && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{errors.confirm_password.message}
</span>
)}
{errors.token && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{errors.token.message}
</span>
)}
{errors.userId && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{errors.userId.message}
</span>
)}
</div>
<input
type="hidden"
id="token"
value={params.get('token') ?? ''}
{...register('token', { required: 'Unable to process: No valid reset token' })}
/>
<input
type="hidden"
id="userId"
value={params.get('userId') ?? ''}
{...register('userId', { required: 'Unable to process: No valid user id' })}
/>
<PasswordInput
id="password"
label={localize('com_auth_password')}
autoComplete="new-password"
register={register('password', {
required: localize('com_auth_password_required'),
minLength: {
value: startupConfig?.minPasswordLength || 8,
message: localize('com_auth_password_min_length'),
},
maxLength: {
value: 128,
message: localize('com_auth_password_max_length'),
},
})}
error={errors.password}
/>
<PasswordInput
id="confirm_password"
label={localize('com_auth_password_confirm')}
autoComplete="new-password"
register={register('confirm_password', {
validate: (value) => value === password || localize('com_auth_password_not_match'),
})}
error={errors.confirm_password}
/>
{errors.token && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{errors.token.message}
</span>
)}
{errors.userId && (
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
{errors.userId.message}
</span>
)}
<div className="mt-6">
<Button
type="submit"

View file

@ -5,3 +5,4 @@ export { default as VerifyEmail } from './VerifyEmail';
export { default as ApiErrorWatcher } from './ApiErrorWatcher';
export { default as RequestPasswordReset } from './RequestPasswordReset';
export { default as TwoFactorScreen } from './TwoFactorScreen';
export { default as PasswordInput } from './PasswordInput';