diff --git a/apps/frontend/app/console/login/components/PhoneLoginMode.tsx b/apps/frontend/app/console/login/components/PhoneLoginMode.tsx deleted file mode 100644 index 7f1a791..0000000 --- a/apps/frontend/app/console/login/components/PhoneLoginMode.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"; -import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"; -import { useState, useCallback } from "react"; -import { toast } from "sonner"; -import LoginHeader from "./LoginHeader"; -import { SendCodeFormData } from "./types"; -import { Label } from "@/components/ui/label" - -export default function PhoneLoginMode({ onSendCode }: { onSendCode: (data: SendCodeFormData) => Promise }) { - const [phone, setPhone] = useState(""); - const handleSendCode = useCallback(() => { - if (phone.trim().length !== 11) { - toast.error('请输入正确的手机号'); - return; - } - onSendCode({ - type: 'phone', - phone, - }) - }, [phone, onSendCode]); - - return ( - <> - -
- - setPhone(e.target.value)} - required /> -
-
-
- -
-
- - - - - - - - - - - -
-
- - - ) -} \ No newline at end of file diff --git a/apps/frontend/app/console/login/components/SmsLoginMode.tsx b/apps/frontend/app/console/login/components/SmsLoginMode.tsx new file mode 100644 index 0000000..9a44ca6 --- /dev/null +++ b/apps/frontend/app/console/login/components/SmsLoginMode.tsx @@ -0,0 +1,78 @@ +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"; +import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp"; +import { useState, useCallback } from "react"; +import { toast } from "sonner"; +import LoginHeader from "./LoginHeader"; +import { Label } from "@/components/ui/label" +import { HumanVerification } from "@/components/human-verification"; +import { AuthAPI, SmsAPI } from "@/lib/api/client"; +import { handleAPIError } from "@/lib/api/common"; + +export default function SmsLoginMode() { + const [phone, setPhone] = useState(""); + const handleSendCode = useCallback(async () => { + await SmsAPI.sendLoginSms(phone) + .then(() => toast.success('验证码已发送!')) + .catch(e => handleAPIError(e, ({ message }) => toast.error(`${message}`))) + }, [phone]); + + return ( + <> + +
+ + setPhone(e.target.value)} + required /> +
+
+
+ +
+
+ + + +
+ + + {[...Array(6)].map((_, i) => ( + + ))} + + +
+
+
+ + + ) +} + +export async function handleSubmit(formData: FormData) { + const phone = formData.get('phone')?.toString() || ''; + const code = formData.get('code')?.toString() || ''; + + return AuthAPI.loginBySms(phone, code) +} \ No newline at end of file diff --git a/apps/frontend/app/console/login/page.tsx b/apps/frontend/app/console/login/page.tsx index f77b1b9..1eb690f 100644 --- a/apps/frontend/app/console/login/page.tsx +++ b/apps/frontend/app/console/login/page.tsx @@ -9,14 +9,14 @@ import { cn } from "@/lib/utils"; import { KeyRound, Phone, FileKey2 } from "lucide-react"; import EmailLoginMode from "./components/EmailLoginMode"; import PasswordLoginMode from "./components/PasswordLoginMode"; -import PhoneLoginMode from "./components/PhoneLoginMode"; +import PhoneLoginMode from "./components/SmsLoginMode"; import { useState } from "react"; import LoginBG from './components/login-bg.jpg'; import Image from "next/image"; import { handleAPIError } from "@/lib/api/common"; import { useUserStore } from "@/store/useUserStore"; -export type SubmitMode = 'password' | 'phone' | 'passkey'; +export type SubmitMode = 'password' | 'sms' | 'passkey'; export default function Login() { const router = useRouter(); @@ -37,6 +37,8 @@ export default function Login() { let handler = (await (async () => { if (loginMode === 'password') { return import('./components/PasswordLoginMode'); + } else if (loginMode === 'sms') { + return import('./components/SmsLoginMode'); } })())?.handleSubmit; if (!handler) { @@ -54,7 +56,7 @@ export default function Login() {
{loginMode === 'password' ? : null} - {/* {loginMode === 'phone' ? : null} */} + {loginMode === 'sms' ? : null} {/* {loginMode === 'email' ? : null} */}
@@ -64,7 +66,7 @@ export default function Login() {
{ - ([['password', KeyRound], ['phone', Phone], ['passkey', FileKey2]] as const).map(([mode, Icon]) => ( + ([['password', KeyRound], ['sms', Phone], ['passkey', FileKey2]] as const).map(([mode, Icon]) => ( @@ -74,7 +76,7 @@ export default function Login() { diff --git a/apps/frontend/lib/api/endpoints/auth.client.ts b/apps/frontend/lib/api/endpoints/auth.client.ts index 9be9a42..7288044 100644 --- a/apps/frontend/lib/api/endpoints/auth.client.ts +++ b/apps/frontend/lib/api/endpoints/auth.client.ts @@ -24,4 +24,29 @@ export async function loginByPassword(identifier: string, password: string) { password, }) }); +} + +export async function loginBySms(phone: string, code: string) { + phone = phone.trim(); + code = code.trim(); + if (phone.length === 0 || code.length === 0) { + throw new APIError('请输入手机号及短信验证码') + } + + if (!/^1[3-9]\d{9}$/.test(phone)) { + throw new APIError('请输入合法的中国大陆手机号'); + } + + + if (! /\d{6}/.test(code)) { + throw new APIError('密码长度只能为6~32位') + } + + return clientFetch<{ user: User }>('/api/auth/login/sms', { + method: 'POST', + body: JSON.stringify({ + phone, + code, + }) + }); } \ No newline at end of file