diff --git a/apps/web-client/src/app/(auth)/forgot-password/page.tsx b/apps/web-client/src/app/(auth)/forgot-password/page.tsx index 08113a63..63548ac7 100644 --- a/apps/web-client/src/app/(auth)/forgot-password/page.tsx +++ b/apps/web-client/src/app/(auth)/forgot-password/page.tsx @@ -107,34 +107,65 @@ export default function ForgotPasswordPage() { }; return ( - // EN: Centered forgot password form layout with dark mode background - // VI: Layout form quên mật khẩu được căn giữa với nền dark mode - <> - -
- - - - {t('auth.forgotPassword.title')} - - - {isSuccess - ? t('auth.forgotPassword.checkEmail') - : t('auth.forgotPassword.description')} - - + // EN: Centered forgot password form layout with cosmic background and glassmorphism + // VI: Layout form quên mật khẩu với nền vũ trụ và hiệu ứng kínhmorphism +
+ {/* EN: Cosmic Background Elements */} + {/* VI: Các thành phần nền vũ trụ */} +
+
+
+
+ + +
+
+
+
+ + + +
+
+

+ {t('auth.forgotPassword.title')} +

+

+ {isSuccess + ? t('auth.forgotPassword.checkEmail') + : t('auth.forgotPassword.description')} +

+
+ +
{isSuccess ? ( // EN: Success state - show confirmation message // VI: Trạng thái thành công - hiển thị thông báo xác nhận - - {/* EN: Success icon and message / VI: Icon và thông báo thành công */} +
- + {t('auth.forgotPassword.resetLinkSent')}
- {/* EN: Detailed success message / VI: Thông báo thành công chi tiết */} -
+

- {t('auth.forgotPassword.resetLinkSentDetail', { email: submittedEmail })} + {t('auth.forgotPassword.resetLinkSentDetail', { + email: submittedEmail, + })}

-
+

{t('auth.forgotPassword.checkInbox')}

- {/* EN: Additional action buttons / VI: Các nút hành động bổ sung */} -
- -
- + + + +
) : ( // EN: Form state - email input and submit // VI: Trạng thái form - input email và submit -
- - {/* EN: API Error message display / VI: Hiển thị thông báo lỗi API */} - {apiError && ( -
- - {apiError} -
- )} - - {/* EN: Email input field / VI: Trường nhập email */} - setValue('email', value)} - onBlur={register('email').onBlur} - autoComplete="email" - autoFocus - aria-required="true" - /> -
- - - {/* EN: Submit button with loading state / VI: Nút submit với trạng thái loading */} - - + + {apiError} +
+ )} + + { + register('email').onChange(e); + setValue('email', e.target.value); + }} + autoComplete="email" + autoFocus + aria-required="true" + /> + + + +
+ + + + + {t('auth.forgotPassword.backToLogin')} + +
)} - - - {/* EN: Back to login link / VI: Link quay lại đăng nhập */} - - ← {t('auth.forgotPassword.backToLogin')} - - - {/* EN: Sign up link / VI: Link đăng ký */} -

- {t('auth.forgotPassword.noAccount')}{' '} - - {t('auth.forgotPassword.signUp')} - -

-
- +
- +
); } diff --git a/apps/web-client/src/app/(auth)/login/page.tsx b/apps/web-client/src/app/(auth)/login/page.tsx index 10137828..038fc8b0 100644 --- a/apps/web-client/src/app/(auth)/login/page.tsx +++ b/apps/web-client/src/app/(auth)/login/page.tsx @@ -108,47 +108,77 @@ export default function LoginPage() { }; return ( - // EN: Centered login form layout with dark mode background - // VI: Layout form đăng nhập được căn giữa với nền dark mode -
+ // EN: Centered login form layout with cosmic background and glassmorphism + // VI: Layout form đăng nhập với nền vũ trụ và hiệu ứng kínhmorphism +
+ {/* EN: Cosmic Background Elements - X.ai style */} + {/* VI: Các thành phần nền vũ trụ - phong cách X.ai */} +
+
+
+
+ -
- - - - {t('auth.login.title')} - - - {t('auth.login.description')} - - -
- - {/* EN: API Error message display / VI: Hiển thị thông báo lỗi API */} - {apiError && ( -
+
+
+
+ + + +
+
+

+ {t('auth.login.title')} +

+

{t('auth.login.description')}

+
+ +
+ + {/* EN: API Error message display / VI: Hiển thị thông báo lỗi API */} + {apiError && ( +
+ {apiError} -
- )} + + + {apiError} +
+ )} +
{/* EN: Email input field / VI: Trường nhập email */} setValue('email', value)} - onBlur={register('email').onBlur} + {...register('email')} + onChange={(e) => { + register('email').onChange(e); + setValue('email', e.target.value); + }} autoComplete="email" aria-required="true" /> {/* EN: Password input field / VI: Trường nhập mật khẩu */} - setValue('password', value)} - onBlur={register('password').onBlur} - autoComplete="current-password" - aria-required="true" - /> +
+ { + register('password').onChange(e); + setValue('password', e.target.value); + }} + autoComplete="current-password" + aria-required="true" + /> - {/* EN: Remember me and forgot password row / VI: Hàng nhớ đăng nhập và quên mật khẩu */} -
- +
+ - - {t('auth.login.forgotPassword')} - + + {t('auth.login.forgotPassword')} + +
- +
- - {/* EN: Submit button with loading state / VI: Nút submit với trạng thái loading */} - + + {/* EN: Sign up link / VI: Link đăng ký */} +

+ {t('auth.login.noAccount')}{' '} + - {isLoading || isSubmitting - ? t('auth.login.signingIn') - : t('auth.login.title')} - - - {/* EN: Sign up link / VI: Link đăng ký */} -

- {t('auth.login.noAccount')}{' '} - - {t('auth.login.signUp')} - -

-
+ {t('auth.login.signUp')} + +

- +
); diff --git a/apps/web-client/src/app/(auth)/register/page.tsx b/apps/web-client/src/app/(auth)/register/page.tsx index bcd8819f..c4ed8dcc 100644 --- a/apps/web-client/src/app/(auth)/register/page.tsx +++ b/apps/web-client/src/app/(auth)/register/page.tsx @@ -222,47 +222,79 @@ export default function RegisterPage() { }; return ( - // EN: Centered register form layout with dark mode background - // VI: Layout form đăng ký được căn giữa với nền dark mode - <> + // EN: Centered register form layout with cosmic background and glassmorphism + // VI: Layout form đăng ký với nền vũ trụ và hiệu ứng kínhmorphism +
+ {/* EN: Cosmic Background Elements */} + {/* VI: Các thành phần nền vũ trụ */} +
+
+
+
+ -
- - - - {t('auth.register.createAccount')} - - - {t('auth.register.signUpToStart')} - - -
- - {/* EN: API Error message display / VI: Hiển thị thông báo lỗi API */} - {apiError && ( -
+
+
+
+ + + +
+
+

+ {t('auth.register.createAccount')} +

+

+ {t('auth.register.signUpToStart')} +

+
+ +
+ + {/* EN: API Error message display / VI: Hiển thị thông báo lỗi API */} + {apiError && ( +
+ {apiError} -
- )} + + + {apiError} +
+ )} +
{/* EN: Full name input field / VI: Trường nhập họ tên */} setValue('fullName', value)} - onBlur={register('fullName').onBlur} + {...register('fullName')} + onChange={(e) => { + register('fullName').onChange(e); + setValue('fullName', e.target.value); + }} autoComplete="name" aria-required="true" /> @@ -285,58 +318,57 @@ export default function RegisterPage() { placeholder="you@example.com" isInvalid={!!errors.email} errorMessage={errors.email?.message} - name="email" - value={watch('email')} - onChange={(value) => setValue('email', value)} - onBlur={register('email').onBlur} + {...register('email')} + onChange={(e) => { + register('email').onChange(e); + setValue('email', e.target.value); + }} autoComplete="email" aria-required="true" /> {/* EN: Password input field / VI: Trường nhập mật khẩu */} -
+
setValue('password', value)} - onBlur={register('password').onBlur} + {...register('password')} + onChange={(e) => { + register('password').onChange(e); + setValue('password', e.target.value); + }} autoComplete="new-password" aria-required="true" /> {/* EN: Password strength indicator / VI: Chỉ báo độ mạnh mật khẩu */} {password && ( -
- {/* EN: Strength bar / VI: Thanh độ mạnh */} -
-
+
+
+ {[1, 2, 3, 4].map((step) => ( +
= step * 25 + ? getStrengthColor(passwordStrength.strength) + : 'bg-glass-subtle' + )} + /> + ))}
- - {/* EN: Strength feedback text / VI: Text phản hồi độ mạnh */}

{t(`auth.register.${passwordStrength.feedback}`)}

@@ -351,21 +383,22 @@ export default function RegisterPage() { placeholder={t('auth.register.reEnterPassword')} isInvalid={!!errors.confirmPassword} errorMessage={errors.confirmPassword?.message} - name="confirmPassword" - value={watch('confirmPassword')} - onChange={(value) => setValue('confirmPassword', value)} - onBlur={register('confirmPassword').onBlur} + {...register('confirmPassword')} + onChange={(e) => { + register('confirmPassword').onChange(e); + setValue('confirmPassword', e.target.value); + }} autoComplete="new-password" aria-required="true" /> {/* EN: Terms and conditions checkbox / VI: Checkbox điều khoản và điều kiện */} -
+
{errors.terms && ( -

- - {errors.terms.message} +

+ {errors.terms.message}

)}
- +
- - {/* EN: Submit button with loading state / VI: Nút submit với trạng thái loading */} - + + {/* EN: Sign in link / VI: Link đăng nhập */} +

+ {t('auth.register.alreadyHaveAccount')}{' '} + - {isLoading || isSubmitting - ? t('auth.register.creatingAccount') - : t('auth.register.createAccount')} - - - {/* EN: Sign in link / VI: Link đăng nhập */} -

- {t('auth.register.alreadyHaveAccount')}{' '} - - {t('auth.register.signIn')} - -

-
+ {t('auth.register.signIn')} + +

- +
- +
); } diff --git a/apps/web-client/src/features/shared/components/ui/input/input.tsx b/apps/web-client/src/features/shared/components/ui/input/input.tsx index ecf8d100..bf982763 100644 --- a/apps/web-client/src/features/shared/components/ui/input/input.tsx +++ b/apps/web-client/src/features/shared/components/ui/input/input.tsx @@ -15,7 +15,7 @@ 'use client'; -import React from 'react'; +import React, { useState } from 'react'; import { TextField as AriaTextField, Label, @@ -24,6 +24,8 @@ import { type TextFieldProps as AriaTextFieldProps, } from 'react-aria-components'; import { cn } from '@/shared/utils'; +import { Eye, EyeOff } from 'lucide-react'; +import { Button } from '../button/button'; export interface InputProps extends Omit { /** @@ -130,6 +132,35 @@ export const Input = React.forwardRef( }, ref ) => { + // EN: Internal state for password visibility + // VI: State nội bộ cho việc hiển thị mật khẩu + const [showPassword, setShowPassword] = useState(false); + + // EN: Determine the actual input type + // VI: Xác định type thực tế của input + const inputType = type === 'password' && showPassword ? 'text' : type; + + // EN: Determine the right element for password toggle + // VI: Xác định element bên phải cho nút gạt mật khẩu + const finalRightElement = + type === 'password' ? ( + + ) : ( + rightElement + ); + return ( ( ( leftElement && 'pl-10', // Right element padding - rightElement && 'pr-10', + finalRightElement && 'pr-10', inputClassName )} /> - {rightElement && ( + {finalRightElement && (
- {rightElement} + {finalRightElement}
)}