Ver código fonte

feat: login form

BaiLuoYan 1 mês atrás
pai
commit
1e878ceab5

+ 79 - 0
src/components/LoginForm/index.tsx

@@ -0,0 +1,79 @@
+import { memo } from 'react';
+
+import { Button, Form, Input } from 'antd';
+import { useTranslation } from 'react-i18next';
+
+import { useResponsive } from '@/hooks/useSize';
+import { userConfigModel } from '@/models/userConfigModel';
+import { setToken } from '@/utils/authUtils';
+
+import { useAction } from './useAction';
+
+export interface LoginFormProps {
+    onSuccess?: () => void;
+}
+
+const LoginForm = memo(({ onSuccess }: LoginFormProps) => {
+    const { t } = useTranslation();
+    const { isMobile } = useResponsive();
+    const { setUserConfig } = userConfigModel.useModel();
+    const [form] = Form.useForm();
+    const { loading, errorMessage, errorKey, handleSubmit } = useAction({
+        setToken,
+        setUserConfig,
+        onSuccess,
+    });
+
+    return (
+        <Form
+            form={form}
+            layout="horizontal"
+            labelCol={{ flex: '0 0 80px' }}
+            className="flex flex-col"
+            onFinish={(values) => handleSubmit(values.username, values.password)}
+            autoComplete="on"
+        >
+            <Form.Item
+                name="username"
+                label={isMobile ? undefined : t('pages.pricing.payFlow.username')}
+                rules={[{ required: true, message: t('pages.pricing.payFlow.loginRequired') }]}
+            >
+                <Input
+                    placeholder={t('pages.pricing.payFlow.username')}
+                    autoComplete="username"
+                    className="bg-white/10 border-white/20 text-white"
+                />
+            </Form.Item>
+            <Form.Item
+                name="password"
+                label={isMobile ? undefined : t('pages.pricing.payFlow.password')}
+                rules={[{ required: true, message: t('pages.pricing.payFlow.loginRequired') }]}
+            >
+                <Input.Password
+                    placeholder={t('pages.pricing.payFlow.password')}
+                    autoComplete="current-password"
+                    className="bg-white/10 border-white/20 text-white"
+                />
+            </Form.Item>
+            {(errorMessage || errorKey) && (
+                <p className="text-red-400 text-sm">
+                    {errorMessage ?? t(`pages.pricing.payFlow.${errorKey}`)}
+                </p>
+            )}
+            <Form.Item className="mt-5 mb-0">
+                <Button
+                    type="primary"
+                    htmlType="submit"
+                    loading={loading}
+                    className="w-full bg-[#0EA5E9] border-none"
+                >
+                    {t('pages.pricing.payFlow.loginSubmit')}
+                </Button>
+            </Form.Item>
+        </Form>
+    );
+});
+
+LoginForm.displayName = 'LoginForm';
+
+export default LoginForm;

+ 62 - 0
src/components/LoginForm/useAction.ts

@@ -0,0 +1,62 @@
+import { useCallback, useState } from 'react';
+
+import { fetchLogin } from '@/services/login';
+
+export interface UseActionParams {
+    setToken: (data: API.UserInfo) => void;
+    setUserConfig: (data: API.UserInfo | null) => void;
+    onSuccess?: () => void;
+}
+
+export type LoginErrorKey = 'loginError' | null;
+
+function getErrorMessage(err: unknown): { message: string | null; errorKey: LoginErrorKey } {
+    const anyErr = err as {
+        name?: string;
+        info?: { errorMessage?: string };
+        response?: { data?: { errorMessage?: string } };
+    };
+    const backendMsg = anyErr?.info?.errorMessage ?? anyErr?.response?.data?.errorMessage ?? null;
+    if (backendMsg && typeof backendMsg === 'string') {
+        return { message: backendMsg, errorKey: null };
+    }
+    return { message: null, errorKey: 'loginError' };
+}
+
+export function useAction({ setToken, setUserConfig, onSuccess }: UseActionParams) {
+    const [loading, setLoading] = useState(false);
+    const [errorMessage, setErrorMessage] = useState<string | null>(null);
+    const [errorKey, setErrorKey] = useState<LoginErrorKey>(null);
+
+    const handleSubmit = useCallback(
+        (username: string, password: string) => {
+            setErrorMessage(null);
+            setErrorKey(null);
+            setLoading(true);
+            fetchLogin({ username: username.trim(), password }, { skipErrorHandler: true })
+                .then((res) => {
+                    const data = res?.data;
+                    if (!data) return;
+                    setToken(data);
+                    setUserConfig(data);
+                    onSuccess?.();
+                })
+                .catch((err) => {
+                    const { message: msg, errorKey: key } = getErrorMessage(err);
+                    setErrorMessage(msg);
+                    setErrorKey(key);
+                })
+                .finally(() => {
+                    setLoading(false);
+                });
+        },
+        [setToken, setUserConfig, onSuccess]
+    );
+
+    return {
+        loading,
+        errorMessage,
+        errorKey,
+        handleSubmit,
+    };
+}