|
@@ -1,9 +1,11 @@
|
|
|
-import CaptchaBox from '@/components/captcha-box';
|
|
|
|
|
|
|
+import CaptchaBox, { CaptchaRes } from '@/components/captcha-box';
|
|
|
import { site } from '@/config.json';
|
|
import { site } from '@/config.json';
|
|
|
|
|
+import { useInterval } from '@/hooks/hooks';
|
|
|
|
|
+import api, { ApiError } from '@/utils/api';
|
|
|
import { signUp, useAuth } from '@/utils/auth';
|
|
import { signUp, useAuth } from '@/utils/auth';
|
|
|
import { Button, Toast } from '@ant-design/react-native';
|
|
import { Button, Toast } from '@ant-design/react-native';
|
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
|
-import { router, useLocalSearchParams } from 'expo-router';
|
|
|
|
|
|
|
+import { Link, router, useLocalSearchParams } from 'expo-router';
|
|
|
import { openBrowserAsync } from 'expo-web-browser';
|
|
import { openBrowserAsync } from 'expo-web-browser';
|
|
|
import React, { useRef, useState } from 'react';
|
|
import React, { useRef, useState } from 'react';
|
|
|
import {
|
|
import {
|
|
@@ -38,18 +40,98 @@ export default function SignUpScreen() {
|
|
|
const [flushAgree, setFlushAgree] = useState(false);
|
|
const [flushAgree, setFlushAgree] = useState(false);
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
|
const scrollView = useRef<ScrollView>(null);
|
|
const scrollView = useRef<ScrollView>(null);
|
|
|
|
|
+ const [captchaVisible, setCaptchaVisible] = useState<boolean>(false);
|
|
|
|
|
+ const [smsTtl, setSmsTtl] = useState(0);
|
|
|
const { setToken } = useAuth();
|
|
const { setToken } = useAuth();
|
|
|
|
|
|
|
|
const { signIn, } = useLocalSearchParams<{ signIn: string; redirectTo?: string }>();
|
|
const { signIn, } = useLocalSearchParams<{ signIn: string; redirectTo?: string }>();
|
|
|
- const handleSendCode = () => {
|
|
|
|
|
|
|
+ const waitCaptcha = useRef<(res: CaptchaRes) => void | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ const needsCaptchaRef = useRef(false);
|
|
|
|
|
+ const handleCaptcha = (res: CaptchaRes) => {
|
|
|
|
|
+
|
|
|
|
|
+ let fun = waitCaptcha.current;
|
|
|
|
|
+ waitCaptcha.current = null;
|
|
|
|
|
+alert(1)
|
|
|
|
|
+ setCaptchaVisible(false);
|
|
|
|
|
+ fun?.(res);
|
|
|
|
|
+ }
|
|
|
|
|
+ const handleSendCode = async () => {
|
|
|
if (mobile.trim().length !== 11) {
|
|
if (mobile.trim().length !== 11) {
|
|
|
Toast.fail('请先输入 11 位手机号');
|
|
Toast.fail('请先输入 11 位手机号');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+ let captcha: CaptchaRes = null!;
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ if (needsCaptchaRef.current) {
|
|
|
|
|
+ setCaptchaVisible(true);
|
|
|
|
|
+ captcha = await new Promise<CaptchaRes>((resolve) => {
|
|
|
|
|
+ waitCaptcha.current = resolve;
|
|
|
|
|
+ });
|
|
|
|
|
+ if (!captcha || !captcha.ok) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await api.post<{
|
|
|
|
|
+ needsCaptcha?: boolean;
|
|
|
|
|
+ timerout: number;
|
|
|
|
|
+ }>("sms/send?__session_id=" + captcha?.sid || '', {
|
|
|
|
|
+ mobile: mobile,
|
|
|
|
|
+ event: 'register',
|
|
|
|
|
+ captcha_code: captcha?.code,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (res.needsCaptcha) {
|
|
|
|
|
+ needsCaptchaRef.current = true;
|
|
|
|
|
+ setCaptchaVisible(true);
|
|
|
|
|
+ captcha = await new Promise<CaptchaRes>((resolve) => {
|
|
|
|
|
+ waitCaptcha.current = resolve;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ alert(JSON.stringify(captcha));
|
|
|
|
|
+ if (!captcha || !captcha.ok) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ setSmsTtl(res.timerout);
|
|
|
|
|
+ break;
|
|
|
|
|
|
|
|
- Toast.success('验证码已发送');
|
|
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (ApiError.isApiError(e)) {
|
|
|
|
|
+ if (e.code === -1) {
|
|
|
|
|
+ Toast.fail("该手机已被注册");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (e.code === -99) {
|
|
|
|
|
+ Toast.fail("图形验证码无效");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Toast.fail("发送验证码失败");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // useEffect(() => {
|
|
|
|
|
+ // let isMount = true;
|
|
|
|
|
+ // setTimeout(()=> {
|
|
|
|
|
+ // if (!isMount) {
|
|
|
|
|
+ // return;
|
|
|
|
|
+ // }
|
|
|
|
|
+ // setSmsTtl((pre)=> pre --);
|
|
|
|
|
+ // })
|
|
|
|
|
+ // return ()=> {
|
|
|
|
|
+ // isMount = false;
|
|
|
|
|
+ // }
|
|
|
|
|
+ // }, [])
|
|
|
|
|
+
|
|
|
|
|
+ useInterval(() => {
|
|
|
|
|
+ setSmsTtl((pre) => pre - 1)
|
|
|
|
|
+ }, smsTtl > 0 ? 1000 : null);
|
|
|
|
|
+
|
|
|
const handleRegister = async () => {
|
|
const handleRegister = async () => {
|
|
|
setFlushAgree(false);
|
|
setFlushAgree(false);
|
|
|
|
|
|
|
@@ -188,15 +270,13 @@ export default function SignUpScreen() {
|
|
|
className="flex-1 p-0 text-xl font-medium text-on-surface"
|
|
className="flex-1 p-0 text-xl font-medium text-on-surface"
|
|
|
/>
|
|
/>
|
|
|
<Pressable
|
|
<Pressable
|
|
|
- disabled={loading}
|
|
|
|
|
|
|
+ disabled={loading || smsTtl > 0}
|
|
|
hitSlop={8}
|
|
hitSlop={8}
|
|
|
onPress={handleSendCode}
|
|
onPress={handleSendCode}
|
|
|
- style={({ pressed }) => ({
|
|
|
|
|
- opacity: loading ? 0.5 : pressed ? 0.72 : 1,
|
|
|
|
|
- })}
|
|
|
|
|
|
|
+
|
|
|
>
|
|
>
|
|
|
- <Text className="text-lg font-bold leading-6 text-primary">
|
|
|
|
|
- 获取验证码
|
|
|
|
|
|
|
+ <Text className={`text-lg font-bold leading-6 ${loading || smsTtl > 0 ? 'text-on-secondary-fixed' : 'text-primary'}`}>
|
|
|
|
|
+ {smsTtl > 0 ? `${smsTtl}s` : '发送验证码'}
|
|
|
</Text>
|
|
</Text>
|
|
|
</Pressable>
|
|
</Pressable>
|
|
|
</View>
|
|
</View>
|
|
@@ -294,10 +374,7 @@ export default function SignUpScreen() {
|
|
|
</View>
|
|
</View>
|
|
|
|
|
|
|
|
<View className="pt-4">
|
|
<View className="pt-4">
|
|
|
- <View
|
|
|
|
|
- className={`flex-row items-start gap-3 rounded-md border-2 border-transparent px-2 transition delay-300 ${flushAgree ? 'border-primary/50' : ''
|
|
|
|
|
- }`}
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <View className={`flex-row items-start gap-3 px-2 rounded-md border-2 transition-colors duration-700 ${flushAgree ? ' border-primary/50' : 'border-transparent'}`}>
|
|
|
<Pressable
|
|
<Pressable
|
|
|
onPress={() => setAgreed((value) => !value)}
|
|
onPress={() => setAgreed((value) => !value)}
|
|
|
hitSlop={8}
|
|
hitSlop={8}
|
|
@@ -316,7 +393,12 @@ export default function SignUpScreen() {
|
|
|
</Pressable>
|
|
</Pressable>
|
|
|
<Text className="flex-1 text-sm leading-6 text-on-surface-variant">
|
|
<Text className="flex-1 text-sm leading-6 text-on-surface-variant">
|
|
|
注册即代表您已阅读并同意
|
|
注册即代表您已阅读并同意
|
|
|
- <Text className="font-semibold text-primary cursor-pointer" onPress={() => openBrowserAsync(`${site}`)}>《用户服务协议》</Text>
|
|
|
|
|
|
|
+ <Link href={{
|
|
|
|
|
+ pathname: '/web',
|
|
|
|
|
+ params: { uri: site }
|
|
|
|
|
+ }}>
|
|
|
|
|
+ <Text className="font-semibold text-primary">《用户服务协议》</Text>
|
|
|
|
|
+ </Link>
|
|
|
、
|
|
、
|
|
|
<Text className="font-semibold text-primary cursor-pointer" onPress={() => openBrowserAsync(`${site}`)}>《隐私政策》</Text>
|
|
<Text className="font-semibold text-primary cursor-pointer" onPress={() => openBrowserAsync(`${site}`)}>《隐私政策》</Text>
|
|
|
,以及授权该应用获取您的公开信息。
|
|
,以及授权该应用获取您的公开信息。
|
|
@@ -326,7 +408,7 @@ export default function SignUpScreen() {
|
|
|
</View>
|
|
</View>
|
|
|
</ScrollView>
|
|
</ScrollView>
|
|
|
</KeyboardAvoidingView>
|
|
</KeyboardAvoidingView>
|
|
|
- <CaptchaBox visible onClose={alert} />
|
|
|
|
|
|
|
+ <CaptchaBox visible={captchaVisible} onClose={handleCaptcha} />
|
|
|
</View>
|
|
</View>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|