diff --git a/web/src/pages/login-next/bg.tsx b/web/src/pages/login-next/bg.tsx new file mode 100644 index 000000000..c9b016c5c --- /dev/null +++ b/web/src/pages/login-next/bg.tsx @@ -0,0 +1,159 @@ +import './index.less'; +const aspectRatio = { + top: 240, + middle: 466, + bottom: 704, +}; + +export const BgSvg = () => { + const def = ( + path: string, + id: number | string = '', + type: keyof typeof aspectRatio, + ) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + {/* */} + + + + + + + + + + + + + + ); + }; + return ( +
+
+
+ {def( + 'M1282.81 -45L999.839 147.611C988.681 155.206 975.496 159.267 961.999 159.267H746.504H330.429C317.253 159.267 304.368 155.397 293.373 148.137L0.88623 -45', + 0, + 'top', + )} +
+
+ {def( + 'M0 1L203.392 203.181C215.992 215.705 233.036 222.736 250.802 222.736H287.103C305.94 222.736 323.913 230.636 336.649 244.514L425.401 341.222C438.137 355.1 456.11 363 474.947 363H976.902C996.333 363 1014.81 354.595 1027.59 339.95L1104.79 251.424C1116.14 238.4 1132.08 230.248 1149.29 228.659L1191.13 224.796C1205.62 223.458 1219.28 217.461 1230.06 207.704L1440 17.7981', + 1, + 'middle', + )} +
+
+ {def( + 'M-10 1L57.1932 71.1509C67.7929 82.2171 74.2953 96.5714 75.6239 111.837L79.8042 159.87C81.3312 177.416 89.68 193.662 103.057 205.117L399.311 458.829C411.497 469.265 427.011 475 443.054 475H972.606C988.463 475 1003.81 469.396 1015.94 459.179L1310.78 210.75C1323.01 200.451 1331.16 186.136 1333.79 170.369L1341.87 121.837C1344.06 108.691 1350.11 96.492 1359.24 86.7885L1440 1', + 2, + 'bottom', + )} +
+
+
+ ); +}; diff --git a/web/src/pages/login-next/form.tsx b/web/src/pages/login-next/form.tsx deleted file mode 100644 index ccd038811..000000000 --- a/web/src/pages/login-next/form.tsx +++ /dev/null @@ -1,267 +0,0 @@ -'use client'; - -import { toast } from '@/components/hooks/use-toast'; -import { Button } from '@/components/ui/button'; -import { Checkbox } from '@/components/ui/checkbox'; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import { - InputOTP, - InputOTPGroup, - InputOTPSlot, -} from '@/components/ui/input-otp'; -import { useTranslate } from '@/hooks/common-hooks'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; - -export function SignUpForm() { - const { t } = useTranslate('login'); - - const FormSchema = z.object({ - email: z.string().email({ - message: t('emailPlaceholder'), - }), - nickname: z.string({ required_error: t('nicknamePlaceholder') }), - password: z.string({ required_error: t('passwordPlaceholder') }), - agree: z.boolean({ required_error: t('passwordPlaceholder') }), - }); - - const form = useForm>({ - resolver: zodResolver(FormSchema), - defaultValues: { - email: '', - }, - }); - - function onSubmit(data: z.infer) { - console.log('🚀 ~ onSubmit ~ data:', data); - toast({ - title: 'You submitted the following values:', - description: ( -
-          {JSON.stringify(data, null, 2)}
-        
- ), - }); - } - - return ( -
- - ( - - {t('emailLabel')} - - - - - - )} - /> - ( - - {t('nicknameLabel')} - - - - - - )} - /> - ( - - {t('passwordLabel')} - - - - - - )} - /> - ( - - - - -
- - I understand and agree to the Terms of Service and Privacy - Policy. - -
-
- )} - /> - - - - ); -} - -export function SignInForm() { - const { t } = useTranslate('login'); - - const FormSchema = z.object({ - email: z.string().email({ - message: t('emailPlaceholder'), - }), - password: z.string({ required_error: t('passwordPlaceholder') }), - }); - - const form = useForm>({ - resolver: zodResolver(FormSchema), - defaultValues: { - email: '', - }, - }); - - function onSubmit(data: z.infer) { - console.log('🚀 ~ onSubmit ~ data:', data); - toast({ - title: 'You submitted the following values:', - description: ( -
-          {JSON.stringify(data, null, 2)}
-        
- ), - }); - } - - return ( -
- - ( - - {t('emailLabel')} - - - - - - )} - /> - ( - - {t('passwordLabel')} - - - - - - )} - /> -
- - -
- - - - ); -} - -export function VerifyEmailForm() { - const FormSchema = z.object({ - pin: z.string().min(6, { - message: 'Your one-time password must be 6 characters.', - }), - }); - - const form = useForm>({ - resolver: zodResolver(FormSchema), - defaultValues: { - pin: '', - }, - }); - - function onSubmit(data: z.infer) { - console.log('🚀 ~ onSubmit ~ data:', data); - toast({ - title: 'You submitted the following values:', - description: ( -
-          {JSON.stringify(data, null, 2)}
-        
- ), - }); - } - - return ( -
- - ( - - One-Time Password - - - - - - - - - - - - - - - )} - /> - - - - - ); -} diff --git a/web/src/pages/login-next/hooks.ts b/web/src/pages/login-next/hooks.ts deleted file mode 100644 index 2641a34b3..000000000 --- a/web/src/pages/login-next/hooks.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useCallback } from 'react'; -import { useSearchParams } from 'umi'; - -export enum Step { - SignIn, - SignUp, - ForgotPassword, - ResetPassword, - VerifyEmail, -} - -export const useSwitchStep = (step: Step) => { - const [_, setSearchParams] = useSearchParams(); - console.log('🚀 ~ useSwitchStep ~ _:', _); - const switchStep = useCallback(() => { - setSearchParams(new URLSearchParams({ step: step.toString() })); - }, [setSearchParams, step]); - - return { switchStep }; -}; diff --git a/web/src/pages/login-next/index.less b/web/src/pages/login-next/index.less new file mode 100644 index 000000000..2322be352 --- /dev/null +++ b/web/src/pages/login-next/index.less @@ -0,0 +1,42 @@ +.animate-glow { + animation: glow 16s infinite linear; +} +.mask-path { + stroke-width: 8; + ::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + stroke-dasharray: 660; + stroke-dashoffset: 0; + stroke: #d11818; + stroke-width: 8; + fill: none; + } +} +@keyframes glow { + 0% { + stroke-dashoffset: 0; + } + 100% { + stroke-dashoffset: -650; + } +} + +@keyframes highlight-flow { + 0% { + stroke-dashoffset: 50; + } + 100% { + stroke-dashoffset: -600; + } /* 15+300-30=285 */ +} + +.animate-highlight { + animation: highlight-flow 16s linear infinite; +} + +////////////////////////////////////////////////////////////////////////// diff --git a/web/src/pages/login-next/index.tsx b/web/src/pages/login-next/index.tsx index a06d29987..35e3e76b4 100644 --- a/web/src/pages/login-next/index.tsx +++ b/web/src/pages/login-next/index.tsx @@ -1,107 +1,305 @@ -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Separator } from '@/components/ui/separator'; -import { useTranslate } from '@/hooks/common-hooks'; -import { DiscordLogoIcon, GitHubLogoIcon } from '@radix-ui/react-icons'; -import { useSearchParams } from 'umi'; -import { SignInForm, SignUpForm, VerifyEmailForm } from './form'; -import { Step, useSwitchStep } from './hooks'; +import SvgIcon from '@/components/svg-icon'; +import { useAuth } from '@/hooks/auth-hooks'; +import { + useLogin, + useLoginChannels, + useLoginWithChannel, + useRegister, +} from '@/hooks/login-hooks'; +import { useSystemConfig } from '@/hooks/system-hooks'; +import { rsaPsw } from '@/utils'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'umi'; -function LoginFooter() { - return ( -
- -

or continue with

-
- - -
-
- ); -} - -export function SignUpCard() { - const { t } = useTranslate('login'); - - const { switchStep } = useSwitchStep(Step.SignIn); - - return ( - - - {t('signUp')} - - - -
- -
- -
-
- ); -} - -export function SignInCard() { - const { t } = useTranslate('login'); - const { switchStep } = useSwitchStep(Step.SignUp); - - return ( - - - {t('login')} - - - - - - - ); -} - -export function VerifyEmailCard() { - // const { t } = useTranslate('login'); - - return ( - - - Verify email - - -
-
-
-

- We’ve sent a 6-digit code to -

-

yifanwu92@gmail.com.

-
- -
- -
-
-
- ); -} +import Spotlight from '@/components/spotlight'; +import { Button, ButtonLoading } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { BgSvg } from './bg'; +import './index.less'; +import { SpotlightTopLeft, SpotlightTopRight } from './spotlight-top'; const Login = () => { - const [searchParams] = useSearchParams(); - const step = Number((searchParams.get('step') ?? Step.SignIn) as Step); + const [title, setTitle] = useState('login'); + const navigate = useNavigate(); + const { login, loading: signLoading } = useLogin(); + const { register, loading: registerLoading } = useRegister(); + const { channels, loading: channelsLoading } = useLoginChannels(); + const { login: loginWithChannel, loading: loginWithChannelLoading } = + useLoginWithChannel(); + const { t } = useTranslation('translation', { keyPrefix: 'login' }); + const loading = + signLoading || + registerLoading || + channelsLoading || + loginWithChannelLoading; + const { config } = useSystemConfig(); + const registerEnabled = config?.registerEnabled !== 0; + + const { isLogin } = useAuth(); + useEffect(() => { + if (isLogin) { + navigate('/'); + } + }, [isLogin, navigate]); + + const handleLoginWithChannel = async (channel: string) => { + await loginWithChannel(channel); + }; + + const changeTitle = () => { + if (title === 'login' && !registerEnabled) { + return; + } + setTitle((title) => (title === 'login' ? 'register' : 'login')); + }; + + const FormSchema = z + .object({ + nickname: z.string().optional(), + email: z + .string() + .email() + .min(1, { message: t('emailPlaceholder') }), + password: z.string().min(1, { message: t('passwordPlaceholder') }), + remember: z.boolean().optional(), + }) + .superRefine((data, ctx) => { + if (title === 'register' && !data.nickname) { + ctx.addIssue({ + path: ['nickname'], + message: 'nicknamePlaceholder', + code: z.ZodIssueCode.custom, + }); + } + }); + const form = useForm({ + defaultValues: { + nickname: '', + email: '', + password: '', + confirmPassword: '', + remember: false, + }, + resolver: zodResolver(FormSchema), + }); + + const onCheck = async (params) => { + console.log('params', params); + try { + // const params = await form.validateFields(); + + const rsaPassWord = rsaPsw(params.password) as string; + + if (title === 'login') { + const code = await login({ + email: `${params.email}`.trim(), + password: rsaPassWord, + }); + if (code === 0) { + navigate('/'); + } + } else { + const code = await register({ + nickname: params.nickname, + email: params.email, + password: rsaPassWord, + }); + if (code === 0) { + setTitle('login'); + } + } + } catch (errorInfo) { + console.log('Failed:', errorInfo); + } + }; return ( -
-
- {step === Step.SignIn && } - {step === Step.SignUp && } - {step === Step.VerifyEmail && } +
+ + + + +
+
+
+ logo +
+ RAGFlow +
+

+ A Leading RAG engine with Agent for superior LLM context. +

+
+ Let's get started +
+
+
+ {/* Logo and Header */} + + {/* Login Form */} +
+

+ {title === 'login' + ? 'Sign in to Your Account' + : 'Create an Account'} +

+
+
+
+ onCheck(data))} + > + ( + + {t('emailLabel')} + + + + + + )} + /> + {title === 'register' && ( + ( + + {t('nicknameLabel')} + + + + + + )} + /> + )} + + ( + + {t('passwordLabel')} + + + + + + )} + /> + + {title === 'login' && ( + ( + + +
+ { + field.onChange(checked); + }} + /> + {t('rememberMe')} +
+
+ +
+ )} + /> + )} + + {title === 'login' ? t('login') : t('continue')} + + {title === 'login' && channels && channels.length > 0 && ( +
+ {channels.map((item) => ( + + ))} +
+ )} + + + + {title === 'login' && registerEnabled && ( +
+

+ {t('signInTip')} + +

+
+ )} + {title === 'register' && ( +
+

+ {t('signUpTip')} + +

+
+ )} +
); diff --git a/web/src/pages/login-next/spotlight-top.tsx b/web/src/pages/login-next/spotlight-top.tsx new file mode 100644 index 000000000..0615f09b5 --- /dev/null +++ b/web/src/pages/login-next/spotlight-top.tsx @@ -0,0 +1,70 @@ +import { useIsDarkTheme } from '@/components/theme-provider'; +import React from 'react'; + +interface SpotlightProps { + className?: string; + opcity?: number; + coverage?: number; +} +/** + * + * @param opcity 0~1 default 0.5 + * @param coverage 0~100 default 60 + * @returns + */ +export const SpotlightTopLeft: React.FC = ({ + className, + opcity = 0.5, + coverage = 60, +}) => { + const isDark = useIsDarkTheme(); + const rgb = isDark ? '255, 255, 255' : '194, 221, 243'; + return ( +
+
+
+ ); +}; +/** + * + * @param opcity 0~1 default 0.5 + * @param coverage 0~100 default 60 + * @returns + */ +export const SpotlightTopRight: React.FC = ({ + className, + opcity = 0.5, + coverage = 60, +}) => { + const isDark = useIsDarkTheme(); + const rgb = isDark ? '255, 255, 255' : '194, 221, 243'; + return ( +
+
+
+ ); +};