mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
feat(login): Refactor the login page and add dynamic background and highlight effects #9869 (#10482)
### What problem does this PR solve? Refactor(login): Refactor the login page and add dynamic background and highlight effects #9869 ### Type of change - [x] Refactoring
This commit is contained in:
159
web/src/pages/login-next/bg.tsx
Normal file
159
web/src/pages/login-next/bg.tsx
Normal file
@ -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 (
|
||||
<svg
|
||||
className="w-full h-full"
|
||||
// style={{ aspectRatio: `1440/${aspectRatio[type]}` }}
|
||||
// preserveAspectRatio="xMinYMid meet"
|
||||
preserveAspectRatio="none"
|
||||
// viewBox={`${getPathBounds(path).minX} 0 ${
|
||||
// getPathBounds(path).width
|
||||
// } ${height}`}
|
||||
viewBox={`0 0 1440 ${aspectRatio[type]}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id={`glow${id}`} x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stopColor="#80FFF8" stopOpacity="0" />
|
||||
<stop offset="50%" stopColor="#80FFF8" stopOpacity="1" />
|
||||
<stop offset="100%" stopColor="#80FFF8" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="strokeWidthGradient"
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="0%"
|
||||
>
|
||||
<stop offset="0%" stopColor="#000" />
|
||||
<stop offset="10%" stopColor="#fff" />
|
||||
<stop offset="50%" stopColor="#fff" />
|
||||
<stop offset="90%" stopColor="#fff" />
|
||||
<stop offset="100%" stopColor="#000" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient
|
||||
id={`highlight${id}`}
|
||||
x1="0%"
|
||||
y1="0%"
|
||||
x2="100%"
|
||||
y2="0%"
|
||||
>
|
||||
<stop offset="45%" stopColor="#FFF" stopOpacity="0.2" />
|
||||
<stop offset="48%" stopColor="#FFD700" stopOpacity="0.3" />
|
||||
</linearGradient>
|
||||
|
||||
<filter
|
||||
id={`glowFilter${id}`}
|
||||
x="-10%"
|
||||
y="-10%"
|
||||
width="120%"
|
||||
height="120%"
|
||||
>
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="5.2" />
|
||||
{/* <feBlend
|
||||
in="blur"
|
||||
in2="SourceGraphic"
|
||||
mode="screen"
|
||||
result="glow"
|
||||
/> */}
|
||||
</filter>
|
||||
<filter
|
||||
id={`highlightFilter${id}`}
|
||||
x="-5%"
|
||||
y="-5%"
|
||||
width="110%"
|
||||
height="110%"
|
||||
>
|
||||
<feGaussianBlur in="SourceGraphic" stdDeviation="5.5" />
|
||||
</filter>
|
||||
<mask id={`glowMask${id}`}>
|
||||
<rect width="100%" height="100%" fill="transparent" />
|
||||
<path
|
||||
d={path}
|
||||
fill="none"
|
||||
stroke="url(#strokeWidthGradient)"
|
||||
strokeWidth="1"
|
||||
strokeDasharray="50,600"
|
||||
strokeDashoffset="0"
|
||||
filter={`url(#glowFilter${id})`}
|
||||
className="animate-glow mask-path"
|
||||
/>
|
||||
<path
|
||||
d={path}
|
||||
fill="none"
|
||||
stroke={`url(#highlight${id})`}
|
||||
strokeWidth="0.5"
|
||||
strokeDasharray="50,600"
|
||||
strokeDashoffset="16"
|
||||
filter={`url(#highlightFilter${id})`}
|
||||
className="animate-highlight mask-path"
|
||||
/>
|
||||
</mask>
|
||||
</defs>
|
||||
<path
|
||||
d={path}
|
||||
stroke="#00BEB4"
|
||||
strokeWidth="1"
|
||||
fill="none"
|
||||
opacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d={path}
|
||||
stroke={`url(#glow${id})`}
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
opacity="0.8"
|
||||
mask={`url(#glowMask${id})`}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none ">
|
||||
<div className="absolute top-0 left-0 right-0 w-full">
|
||||
<div
|
||||
className={`w-full ml-10`}
|
||||
style={{ height: aspectRatio['top'] + 'px' }}
|
||||
>
|
||||
{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',
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`w-full -mt-40`}
|
||||
style={{ height: aspectRatio['middle'] + 'px' }}
|
||||
>
|
||||
{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',
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`w-full -mt-52`}
|
||||
style={{ height: aspectRatio['bottom'] + 'px' }}
|
||||
>
|
||||
{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',
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -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<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
email: '',
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('emailLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('emailPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nickname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('nicknameLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('nicknamePlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('passwordLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type={'password'}
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="agree"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>
|
||||
I understand and agree to the Terms of Service and Privacy
|
||||
Policy.
|
||||
</FormLabel>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit" className="w-full">
|
||||
{t('signUp')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
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<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
email: '',
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('emailLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('emailPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('passwordLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type={'password'}
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox id="terms" />
|
||||
<label
|
||||
htmlFor="terms"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
{t('rememberMe')}
|
||||
</label>
|
||||
</div>
|
||||
<Button type="submit" className="w-full">
|
||||
{t('login')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export function VerifyEmailForm() {
|
||||
const FormSchema = z.object({
|
||||
pin: z.string().min(6, {
|
||||
message: 'Your one-time password must be 6 characters.',
|
||||
}),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
pin: '',
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
console.log('🚀 ~ onSubmit ~ data:', data);
|
||||
toast({
|
||||
title: 'You submitted the following values:',
|
||||
description: (
|
||||
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
|
||||
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
|
||||
</pre>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="pin"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>One-Time Password</FormLabel>
|
||||
<FormControl>
|
||||
<InputOTP maxLength={6} {...field}>
|
||||
<InputOTPGroup>
|
||||
<InputOTPSlot index={0} />
|
||||
<InputOTPSlot index={1} />
|
||||
<InputOTPSlot index={2} />
|
||||
<InputOTPSlot index={3} />
|
||||
<InputOTPSlot index={4} />
|
||||
<InputOTPSlot index={5} />
|
||||
</InputOTPGroup>
|
||||
</InputOTP>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
Verify
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@ -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 };
|
||||
};
|
||||
42
web/src/pages/login-next/index.less
Normal file
42
web/src/pages/login-next/index.less
Normal file
@ -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;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@ -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';
|
||||
|
||||
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 [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);
|
||||
}
|
||||
};
|
||||
|
||||
function LoginFooter() {
|
||||
return (
|
||||
<section className="pt-4">
|
||||
<Separator />
|
||||
<p className="text-center pt-4">or continue with</p>
|
||||
<div className="flex gap-4 justify-center pt-[20px]">
|
||||
<GitHubLogoIcon className="w-8 h-8"></GitHubLogoIcon>
|
||||
<DiscordLogoIcon className="w-8 h-8"></DiscordLogoIcon>
|
||||
<div className="min-h-screen relative overflow-hidden">
|
||||
<BgSvg />
|
||||
<Spotlight opcity={0.6} coverage={60} />
|
||||
<SpotlightTopLeft opcity={0.2} coverage={20} />
|
||||
<SpotlightTopRight opcity={0.2} coverage={20} />
|
||||
<div className="absolute top-3 flex flex-col items-center mb-12 w-full text-text-primary">
|
||||
<div className="flex items-center mb-4 w-full pl-10 pt-10 ">
|
||||
<div className="w-10 h-10 rounded-lg border flex items-center justify-center mr-3">
|
||||
<img
|
||||
src={'/logo.svg'}
|
||||
alt="logo"
|
||||
className="size-10 mr-[12] cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function SignUpCard() {
|
||||
const { t } = useTranslate('login');
|
||||
|
||||
const { switchStep } = useSwitchStep(Step.SignIn);
|
||||
|
||||
return (
|
||||
<Card className="w-[400px]">
|
||||
<CardHeader>
|
||||
<CardTitle>{t('signUp')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SignUpForm></SignUpForm>
|
||||
<div className="text-center">
|
||||
<Button variant={'link'} className="pt-6" onClick={switchStep}>
|
||||
Already have an account? Log In
|
||||
</Button>
|
||||
<span className="text-xl font-bold self-end">RAGFlow</span>
|
||||
</div>
|
||||
<LoginFooter></LoginFooter>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
<h1 className="text-2xl font-bold text-center mb-2">
|
||||
A Leading RAG engine with Agent for superior LLM context.
|
||||
</h1>
|
||||
<div className="mt-4 px-6 py-1 text-sm font-medium text-cyan-600 border border-accent-primary rounded-full hover:bg-cyan-50 transition-colors duration-200 border-glow relative overflow-hidden">
|
||||
Let's get started
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-10 flex flex-col items-center justify-center min-h-screen px-4 sm:px-6 lg:px-8">
|
||||
{/* Logo and Header */}
|
||||
|
||||
export function SignInCard() {
|
||||
const { t } = useTranslate('login');
|
||||
const { switchStep } = useSwitchStep(Step.SignUp);
|
||||
{/* Login Form */}
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-xl font-semibold text-text-primary">
|
||||
{title === 'login'
|
||||
? 'Sign in to Your Account'
|
||||
: 'Create an Account'}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="w-full max-w-md bg-bg-base backdrop-blur-sm rounded-2xl shadow-xl p-8 border border-border-button">
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="space-y-6"
|
||||
onSubmit={form.handleSubmit((data) => onCheck(data))}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('emailLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('emailPlaceholder')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{title === 'register' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nickname"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('nicknameLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('nicknamePlaceholder')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
return (
|
||||
<Card className="w-[400px]">
|
||||
<CardHeader>
|
||||
<CardTitle>{t('login')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<SignInForm></SignInForm>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>{t('passwordLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{title === 'login' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="remember"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<div className="flex gap-2">
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange(checked);
|
||||
}}
|
||||
/>
|
||||
<FormLabel>{t('rememberMe')}</FormLabel>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<ButtonLoading
|
||||
type="submit"
|
||||
loading={loading}
|
||||
className="bg-metallic-gradient border-b-[#00BEB4] border-b-2 hover:bg-metallic-gradient hover:border-b-[#02bcdd] w-full"
|
||||
>
|
||||
{title === 'login' ? t('login') : t('continue')}
|
||||
</ButtonLoading>
|
||||
{title === 'login' && channels && channels.length > 0 && (
|
||||
<div className="mt-3 border">
|
||||
{channels.map((item) => (
|
||||
<Button
|
||||
className="w-full mt-2"
|
||||
onClick={switchStep}
|
||||
variant={'secondary'}
|
||||
variant={'transparent'}
|
||||
key={item.channel}
|
||||
onClick={() => handleLoginWithChannel(item.channel)}
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<SvgIcon
|
||||
name={item.icon || 'sso'}
|
||||
width={20}
|
||||
height={20}
|
||||
style={{ marginRight: 5 }}
|
||||
/>
|
||||
Sign in with {item.display_name}
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
{title === 'login' && registerEnabled && (
|
||||
<div className="mt-6 text-right">
|
||||
<p className="text-text-disabled text-sm">
|
||||
{t('signInTip')}
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
onClick={changeTitle}
|
||||
className="text-cyan-600 hover:text-cyan-800 font-medium border-none transition-colors duration-200"
|
||||
>
|
||||
{t('signUp')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export function VerifyEmailCard() {
|
||||
// const { t } = useTranslate('login');
|
||||
|
||||
return (
|
||||
<Card className="w-[400px]">
|
||||
<CardHeader>
|
||||
<CardTitle>Verify email</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<section className="flex gap-y-6 flex-col">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">
|
||||
We’ve sent a 6-digit code to
|
||||
</p>
|
||||
<p className="text-sm text-blue-500">yifanwu92@gmail.com.</p>
|
||||
</div>
|
||||
<Button>Resend</Button>
|
||||
)}
|
||||
{title === 'register' && (
|
||||
<div className="mt-6 text-right">
|
||||
<p className="text-text-disabled text-sm">
|
||||
{t('signUpTip')}
|
||||
<Button
|
||||
variant={'transparent'}
|
||||
onClick={changeTitle}
|
||||
className="text-cyan-600 hover:text-cyan-800 font-medium border-none transition-colors duration-200"
|
||||
>
|
||||
{t('login')}
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<VerifyEmailForm></VerifyEmailForm>
|
||||
</section>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const Login = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const step = Number((searchParams.get('step') ?? Step.SignIn) as Step);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex items-center pl-[15%] bg-[url('@/assets/svg/next-login-bg.svg')] bg-cover bg-center">
|
||||
<div className="inline-block bg-colors-background-neutral-standard rounded-lg">
|
||||
{step === Step.SignIn && <SignInCard></SignInCard>}
|
||||
{step === Step.SignUp && <SignUpCard></SignUpCard>}
|
||||
{step === Step.VerifyEmail && <VerifyEmailCard></VerifyEmailCard>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
70
web/src/pages/login-next/spotlight-top.tsx
Normal file
70
web/src/pages/login-next/spotlight-top.tsx
Normal file
@ -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<SpotlightProps> = ({
|
||||
className,
|
||||
opcity = 0.5,
|
||||
coverage = 60,
|
||||
}) => {
|
||||
const isDark = useIsDarkTheme();
|
||||
const rgb = isDark ? '255, 255, 255' : '194, 221, 243';
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
|
||||
style={{
|
||||
backdropFilter: 'blur(30px)',
|
||||
zIndex: -1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: `radial-gradient(circle at 10% -10%, rgba(${rgb},${opcity}) 0%, rgba(${rgb},0) ${coverage}%)`,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @param opcity 0~1 default 0.5
|
||||
* @param coverage 0~100 default 60
|
||||
* @returns
|
||||
*/
|
||||
export const SpotlightTopRight: React.FC<SpotlightProps> = ({
|
||||
className,
|
||||
opcity = 0.5,
|
||||
coverage = 60,
|
||||
}) => {
|
||||
const isDark = useIsDarkTheme();
|
||||
const rgb = isDark ? '255, 255, 255' : '194, 221, 243';
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
|
||||
style={{
|
||||
backdropFilter: 'blur(30px)',
|
||||
zIndex: -1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background: `radial-gradient(circle at 90% -10%, rgba(${rgb},${opcity}) 0%, rgba(${rgb},0) ${coverage}%)`,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user