diff --git a/apps/user/app/(main)/(content)/register/page.tsx b/apps/user/app/(main)/(content)/register/page.tsx
new file mode 100644
index 0000000..29d4b87
--- /dev/null
+++ b/apps/user/app/(main)/(content)/register/page.tsx
@@ -0,0 +1,10 @@
+'use client';
+import EmailAuthForm2 from '@/app/auth/email2/auth-form';
+
+export default function RegisterPage() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/user/app/auth/email2/auth-form.tsx b/apps/user/app/auth/email2/auth-form.tsx
new file mode 100644
index 0000000..01431c2
--- /dev/null
+++ b/apps/user/app/auth/email2/auth-form.tsx
@@ -0,0 +1,111 @@
+'use client';
+
+import { resetPassword, userLogin, userRegister } from '@/services/common/auth';
+import { useTranslations } from 'next-intl';
+import { useRouter } from 'next/navigation';
+import { ReactNode, useState, useTransition } from 'react';
+import { toast } from 'sonner';
+
+import {
+ NEXT_PUBLIC_DEFAULT_USER_EMAIL,
+ NEXT_PUBLIC_DEFAULT_USER_PASSWORD,
+} from '@/config/constants';
+import useGlobalStore from '@/config/use-global';
+import { getRedirectUrl, setAuthorization } from '@/utils/common';
+import LoginForm from './login-form';
+import RegisterForm from './register-form';
+import ResetForm from './reset-form';
+
+export default function EmailAuthForm(props: { isRedirect: boolean }) {
+ const t = useTranslations('auth');
+ const router = useRouter();
+ const [type, setType] = useState<'login' | 'register' | 'reset'>('register');
+ const [loading, startTransition] = useTransition();
+ const [initialValues, setInitialValues] = useState<{
+ email?: string;
+ password?: string;
+ }>({
+ email: NEXT_PUBLIC_DEFAULT_USER_EMAIL,
+ password: NEXT_PUBLIC_DEFAULT_USER_PASSWORD,
+ });
+ const { getUserInfo } = useGlobalStore();
+ const handleFormSubmit = async (params: any) => {
+ const onLogin = async (token?: string) => {
+ if (!token) return;
+ setAuthorization(token);
+ console.log('props.isRedirect', token);
+ console.log('props.isRedirect', props.isRedirect);
+ console.log('props.isRedirect ', getRedirectUrl());
+ if (props.isRedirect) {
+ router.replace(getRedirectUrl());
+ router.refresh();
+ } else {
+ await getUserInfo();
+ }
+ };
+ startTransition(async () => {
+ try {
+ switch (type) {
+ case 'login': {
+ const login = await userLogin(params);
+ toast.success(t('login.success'));
+ onLogin(login.data.data?.token);
+ break;
+ }
+ case 'register': {
+ const create = await userRegister(params);
+ toast.success(t('register.success'));
+ onLogin(create.data.data?.token);
+ break;
+ }
+ case 'reset':
+ await resetPassword(params);
+ toast.success(t('reset.success'));
+ setType('login');
+ break;
+ }
+ } catch (error) {
+ /* empty */
+ }
+ });
+ };
+
+ let UserForm: ReactNode = null;
+ switch (type) {
+ case 'login':
+ UserForm = (
+
+ );
+ break;
+ case 'register':
+ UserForm = (
+
+ );
+ break;
+ case 'reset':
+ UserForm = (
+
+ );
+ break;
+ }
+
+ return UserForm;
+}
diff --git a/apps/user/app/auth/email2/login-form.tsx b/apps/user/app/auth/email2/login-form.tsx
new file mode 100644
index 0000000..859f1a6
--- /dev/null
+++ b/apps/user/app/auth/email2/login-form.tsx
@@ -0,0 +1,147 @@
+import useGlobalStore from '@/config/use-global';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { AiroButton } from '@workspace/airo-ui/components/AiroButton';
+import { Button } from '@workspace/airo-ui/components/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@workspace/airo-ui/components/form';
+import { Input } from '@workspace/airo-ui/components/input';
+import { Icon } from '@workspace/airo-ui/custom-components/icon';
+import { useTranslations } from 'next-intl';
+import { Dispatch, SetStateAction, useRef } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
+
+export default function LoginForm({
+ loading,
+ onSubmit,
+ initialValues,
+ setInitialValues,
+ onSwitchForm,
+}: {
+ loading?: boolean;
+ onSubmit: (data: any) => void;
+ initialValues: any;
+ setInitialValues: Dispatch>;
+ onSwitchForm: Dispatch>;
+}) {
+ const t = useTranslations('auth.login');
+ const { common } = useGlobalStore();
+ const { verify } = common;
+
+ const formSchema = z.object({
+ email: z.string().email(t('email')),
+ password: z.string(),
+ cf_token:
+ verify.enable_login_verify && verify.turnstile_site_key ? z.string() : z.string().optional(),
+ });
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: initialValues,
+ });
+
+ const turnstile = useRef(null);
+ const handleSubmit = form.handleSubmit((data) => {
+ try {
+ onSubmit(data);
+ } catch (error) {
+ turnstile.current?.reset();
+ }
+ });
+
+ return (
+ <>
+ 账户验证
+
+
+ >
+ );
+}
diff --git a/apps/user/app/auth/email2/register-form.tsx b/apps/user/app/auth/email2/register-form.tsx
new file mode 100644
index 0000000..80d8b01
--- /dev/null
+++ b/apps/user/app/auth/email2/register-form.tsx
@@ -0,0 +1,262 @@
+'use client';
+import useGlobalStore from '@/config/use-global';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { AiroButton } from '@workspace/airo-ui/components/AiroButton';
+import { Button } from '@workspace/airo-ui/components/button';
+
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@workspace/airo-ui/components/form';
+import { Input } from '@workspace/airo-ui/components/input';
+import { Icon } from '@workspace/airo-ui/custom-components/icon';
+import { Markdown } from '@workspace/airo-ui/custom-components/markdown';
+import { useTranslations } from 'next-intl';
+import { useRouter } from 'next/navigation';
+import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+import SendCode from '../send-code';
+import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
+
+export default function RegisterForm({
+ loading,
+ onSubmit,
+ initialValues,
+ setInitialValues,
+ onSwitchForm,
+}: {
+ loading?: boolean;
+ onSubmit: (data: any) => void;
+ initialValues: any;
+ setInitialValues: Dispatch>;
+ onSwitchForm: Dispatch>;
+}) {
+ const t = useTranslations('auth.register');
+ const { common } = useGlobalStore();
+ const { verify, auth, invite } = common;
+ const router = useRouter();
+ const handleCheckUser = async (email: string) => {
+ try {
+ if (!auth.email.enable_domain_suffix) return true;
+ const domain = email.split('@')[1];
+ const isValid = auth.email?.domain_suffix_list.split('\n').includes(domain || '');
+ return isValid;
+ } catch (error) {
+ console.log('Error checking user:', error);
+ return false;
+ }
+ };
+
+ const formSchema = z
+ .object({
+ email: z
+ .string()
+ .email(t('email'))
+ .refine(handleCheckUser, {
+ message: t('whitelist'),
+ }),
+ password: z.string().min(1, '请输入密码'), // 必填提示
+ repeat_password: z.string().min(1, '请重复输入密码'), // 必填
+ code: auth.email.enable_verify
+ ? z.string().min(1, '请输入验证码') // 必填
+ : z.string().nullish(),
+ invite: invite.forced_invite ? z.string().min(1, '请输入邀请码') : z.string().nullish(),
+ cf_token:
+ verify.enable_register_verify && verify.turnstile_site_key
+ ? z.string()
+ : z.string().nullish(),
+ })
+ .superRefine(({ password, repeat_password }, ctx) => {
+ if (password !== repeat_password) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: t('passwordMismatch'),
+ path: ['repeat_password'],
+ });
+ }
+ });
+
+ const [inviteDefault, setInviteDefault] = useState('');
+
+ useEffect(() => {
+ const invite = localStorage.getItem('invite') || '';
+ setInviteDefault(invite);
+ }, []);
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ ...initialValues,
+ invite: inviteDefault,
+ },
+ });
+
+ const turnstile = useRef(null);
+ const handleSubmit = form.handleSubmit((data) => {
+ try {
+ onSubmit(data);
+ } catch (error) {
+ turnstile.current?.reset();
+ }
+ });
+
+ return (
+ <>
+ 线路优化
+
+ {auth.register.stop_register ? (
+ {t('message')}
+ ) : (
+
+
+ )}
+ >
+ );
+}
diff --git a/apps/user/app/auth/email2/reset-form.tsx b/apps/user/app/auth/email2/reset-form.tsx
new file mode 100644
index 0000000..5cadaa8
--- /dev/null
+++ b/apps/user/app/auth/email2/reset-form.tsx
@@ -0,0 +1,174 @@
+import useGlobalStore from '@/config/use-global';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { AiroButton } from '@workspace/airo-ui/components/AiroButton';
+import { Button } from '@workspace/airo-ui/components/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@workspace/airo-ui/components/form';
+import { Input } from '@workspace/airo-ui/components/input';
+import { Icon } from '@workspace/airo-ui/custom-components/icon';
+import { useTranslations } from 'next-intl';
+import { Dispatch, SetStateAction, useRef } from 'react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+import SendCode from '../send-code';
+import CloudFlareTurnstile, { TurnstileRef } from '../turnstile';
+
+export default function ResetForm({
+ loading,
+ onSubmit,
+ initialValues,
+ setInitialValues,
+ onSwitchForm,
+}: {
+ loading?: boolean;
+ onSubmit: (data: any) => void;
+ initialValues: any;
+ setInitialValues: Dispatch>;
+ onSwitchForm: Dispatch>;
+}) {
+ const t = useTranslations('auth.reset');
+
+ const { common } = useGlobalStore();
+ const { verify, auth } = common;
+
+ const formSchema = z.object({
+ email: z.string().email(t('email')),
+ password: z.string(),
+ code: auth?.email?.enable_verify ? z.string() : z.string().nullish(),
+ cf_token:
+ verify.enable_register_verify && verify.turnstile_site_key
+ ? z.string()
+ : z.string().nullish(),
+ });
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ defaultValues: initialValues,
+ });
+
+ const turnstile = useRef(null);
+ const handleSubmit = form.handleSubmit((data) => {
+ try {
+ onSubmit(data);
+ } catch (error) {
+ turnstile.current?.reset();
+ }
+ });
+
+ return (
+ <>
+ 找回账户
+
+
+ >
+ );
+}
diff --git a/apps/user/utils/common.ts b/apps/user/utils/common.ts
index 4a7a362..d017e05 100644
--- a/apps/user/utils/common.ts
+++ b/apps/user/utils/common.ts
@@ -53,7 +53,7 @@ export function Logout() {
Crisp.session.reset(); // 2. Unbind the current session
const pathname = location.pathname;
if (
- !['', '/', '/auth', '/tos', '/privacy-policy'].includes(pathname) &&
+ !['', '/', '/auth', '/tos', '/privacy-policy', '/register'].includes(pathname) &&
!pathname.startsWith('/purchasing') &&
!pathname.startsWith('/oauth/')
) {