+
{
+ window.location.href = '/';
+ }}
+ className={'flex cursor-pointer'}
+ >
+
+
+
{t('resetPassword.title')}
+
+ {t('resetPassword.description')}
+
+
+
+ {
+ setEmail(e.target.value);
+ }}
+ value={email}
+ placeholder={t('resetPassword.placeholder')}
+ type='email'
+ onKeyDown={(e) => {
+ if (createHotkey(HOT_KEY_NAME.ENTER)(e.nativeEvent)) {
+ void handleSubmit(e);
+ }
+ }}
+ />
+
+
+
+
+
+ );
+}
diff --git a/src/components/login/Login.tsx b/src/components/login/Login.tsx
index 976fb671..6cc6ae67 100644
--- a/src/components/login/Login.tsx
+++ b/src/components/login/Login.tsx
@@ -1,9 +1,9 @@
import LoginProvider from '@/components/login/LoginProvider';
-import MagicLink from '@/components/login/MagicLink';
import { Separator } from '@/components/ui/separator';
import { ReactComponent as Logo } from '@/assets/icons/logo.svg';
import { useTranslation } from 'react-i18next';
import { ReactComponent as ArrowRight } from '@/assets/icons/arrow_right.svg';
+import EmailLogin from '@/components/login/EmailLogin';
export function Login ({ redirectTo }: { redirectTo: string }) {
const { t } = useTranslation();
@@ -20,7 +20,7 @@ export function Login ({ redirectTo }: { redirectTo: string }) {
{t('web.or')}
diff --git a/src/components/login/const.ts b/src/components/login/const.ts
new file mode 100644
index 00000000..1b5bba2d
--- /dev/null
+++ b/src/components/login/const.ts
@@ -0,0 +1,7 @@
+export const LOGIN_ACTION = {
+ CHECK_EMAIL: 'checkEmail',
+ ENTER_PASSWORD: 'enterPassword',
+ RESET_PASSWORD: 'resetPassword',
+ CHECK_EMAIL_RESET_PASSWORD: 'checkEmailResetPassword',
+ CHANGE_PASSWORD: 'changePassword',
+} as const;
\ No newline at end of file
diff --git a/src/components/ui/password-input.tsx b/src/components/ui/password-input.tsx
new file mode 100644
index 00000000..f761681f
--- /dev/null
+++ b/src/components/ui/password-input.tsx
@@ -0,0 +1,121 @@
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
+import { forwardRef } from 'react';
+
+import { ReactComponent as HideIcon } from '@/assets/icons/hide.svg';
+import { ReactComponent as ShowIcon } from '@/assets/icons/show.svg';
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+
+// Base input styles that apply to all variants and sizes
+const baseInputStyles = cn(
+ // Text and placeholder styling
+ 'text-text-primary placeholder:text-text-tertiary',
+
+ // Selection styling
+ 'selection:bg-fill-theme-thick selection:text-text-on-fill focus:caret-fill-theme-thick',
+
+ 'bg-fill-content',
+
+ // Layout
+ 'flex min-w-0',
+
+ // Typography
+ 'text-sm',
+
+ // Effects
+ 'outline-none',
+
+ // File input styling
+ 'file:inline-flex file:border-0 file:bg-fill-content file:text-sm file:font-medium',
+
+ // Disabled state
+ 'disabled:pointer-events-none disabled:cursor-not-allowed'
+);
+
+const inputVariants = cva('flex items-center gap-1', {
+ variants: {
+ variant: {
+ // Default variant with focus styles
+ default:
+ 'border-border-primary border data-[focused=true]:border-border-theme-thick data-[focused=true]:ring-border-theme-thick data-[focused=true]:ring-[0.5px] disabled:border-border-primary disabled:bg-fill-primary-hover disabled:text-text-tertiary hover:border-border-primary-hover',
+ // Destructive variant for error states
+ destructive:
+ 'border border-border-error-thick focus-visible:border-border-error-thick focus-visible:ring-border-error-thick focus-visible:ring-[0.5px] focus:caret-text-primary disabled:border-border-primary disabled:bg-fill-primary-hover disabled:text-text-tertiary',
+ },
+ size: {
+ // Small size input
+ sm: 'h-8 px-2 rounded-300',
+
+ // Medium size input (default)
+ md: 'h-10 px-2 rounded-400',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'sm',
+ },
+});
+
+export interface PasswordInputProps
+ extends Omit
, 'size' | 'variant' | 'type'>,
+ VariantProps {
+ inputProps?: React.InputHTMLAttributes;
+ inputRef?: React.Ref;
+}
+
+const PasswordInput = forwardRef(
+ ({ className, variant, size, inputProps, inputRef, ...props }, ref) => {
+ const [focused, setFocused] = React.useState(false);
+ const [showPassword, setShowPassword] = React.useState(false);
+
+ const togglePasswordVisibility = (e: React.MouseEvent) => {
+ e.preventDefault();
+ setShowPassword(!showPassword);
+ };
+
+ return (
+
+ {
+ setFocused(true);
+ props?.onFocus?.(e);
+ }}
+ onBlur={(e) => {
+ setFocused(false);
+ props?.onBlur?.(e);
+ }}
+ />
+
+
+ );
+ }
+);
+
+PasswordInput.displayName = 'PasswordInput';
+
+export { PasswordInput, inputVariants, baseInputStyles };
diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx
index 592cbeb5..152c912a 100644
--- a/src/pages/LoginPage.tsx
+++ b/src/pages/LoginPage.tsx
@@ -1,8 +1,13 @@
-import { useContext, useEffect } from 'react';
+import { useContext, useEffect, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { Login } from '@/components/login';
+import { ChangePassword } from '@/components/login/ChangePassword';
import CheckEmail from '@/components/login/CheckEmail';
+import CheckEmailResetPassword from '@/components/login/CheckEmailResetPassword';
+import { LOGIN_ACTION } from '@/components/login/const';
+import { EnterPassword } from '@/components/login/EnterPassword';
+import { ForgotPassword } from '@/components/login/ForgotPassword';
import { AFConfigContext } from '@/components/main/app.hooks';
function LoginPage() {
@@ -10,21 +15,38 @@ function LoginPage() {
const redirectTo = search.get('redirectTo') || '';
const action = search.get('action') || '';
const email = search.get('email') || '';
+ const force = search.get('force') === 'true';
const isAuthenticated = useContext(AFConfigContext)?.isAuthenticated || false;
useEffect(() => {
+ if (action === LOGIN_ACTION.CHANGE_PASSWORD || force) {
+ return;
+ }
+
if (isAuthenticated && redirectTo && decodeURIComponent(redirectTo) !== window.location.href) {
window.location.href = decodeURIComponent(redirectTo);
}
- }, [isAuthenticated, redirectTo]);
+ }, [action, force, isAuthenticated, redirectTo]);
+
+ const renderContent = useMemo(() => {
+ switch (action) {
+ case LOGIN_ACTION.CHECK_EMAIL:
+ return ;
+ case LOGIN_ACTION.ENTER_PASSWORD:
+ return ;
+ case LOGIN_ACTION.RESET_PASSWORD:
+ return ;
+ case LOGIN_ACTION.CHECK_EMAIL_RESET_PASSWORD:
+ return ;
+ case LOGIN_ACTION.CHANGE_PASSWORD:
+ return ;
+ default:
+ return ;
+ }
+ }, [action, email, redirectTo]);
+
return (
-
- {action === 'checkEmail' ? (
-
- ) : (
-
- )}
-
+ {renderContent}
);
}