296 lines
12 KiB
TypeScript
296 lines
12 KiB
TypeScript
import React, { useState, useMemo, useCallback } from 'react'
|
||
import { Block, Text, Toast, VideoBox } from '@share/components'
|
||
import { Ionicons } from '@expo/vector-icons'
|
||
import { ScrollView, Dimensions, TextInput, Platform } from 'react-native'
|
||
import { useAuth } from '@/hooks/core/use-auth'
|
||
import { KeyboardAvoidingView } from 'react-native-keyboard-controller'
|
||
import { setAuthToken } from '@/lib/auth'
|
||
|
||
|
||
const BACKGROUND_VIDEOS = [
|
||
'https://cdn.roasmax.cn/material/b46f380532e14cf58dd350dbacc7c34a.mp4',
|
||
'https://cdn.roasmax.cn/material/992e6c5d940c42feb71c27e556b754c0.mp4',
|
||
'https://cdn.roasmax.cn/material/e4947477843f4067be7c37569a33d17b.mp4',
|
||
]
|
||
|
||
|
||
|
||
type AuthMode = 'login' | 'register'
|
||
|
||
export default function Auth() {
|
||
const { signIn, signUp } = useAuth()
|
||
const [mode, setMode] = useState<AuthMode>('login')
|
||
const [bgVideo] = useState(() => BACKGROUND_VIDEOS[Math.floor(Math.random() * BACKGROUND_VIDEOS.length)])
|
||
|
||
const [username, setUsername] = useState('')
|
||
const [email, setEmail] = useState('')
|
||
const [password, setPassword] = useState('')
|
||
const [confirmPassword, setConfirmPassword] = useState('')
|
||
const [loading, setLoading] = useState(false)
|
||
|
||
const { height: screenHeight } = Dimensions.get('window')
|
||
|
||
const handleLogin = useCallback(async () => {
|
||
if (!username || !password) {
|
||
Toast.show({ title: '请填写账号和密码' })
|
||
return
|
||
}
|
||
|
||
setLoading(true)
|
||
try {
|
||
const result = await signIn.username({
|
||
username,
|
||
password,
|
||
}, {
|
||
onSuccess: async (ctx) => {
|
||
const authToken = ctx.response.headers.get("set-auth-token");
|
||
if (authToken) {
|
||
setAuthToken(authToken)
|
||
}
|
||
},
|
||
onError: (ctx) => {
|
||
console.error(`[LOGIN] login error`, ctx)
|
||
}
|
||
})
|
||
|
||
if (result.error) {
|
||
Toast.show({ title: result.error.message || '登录失败' })
|
||
} else {
|
||
Toast.show({ title: '登录成功!' })
|
||
}
|
||
} catch (error: any) {
|
||
Toast.show({ title: error.message || '登录失败' })
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}, [username, password, signIn])
|
||
|
||
const handleRegister = useCallback(async () => {
|
||
if (!email || !username || !password) {
|
||
Toast.show({ title: '请填写所有必填项' })
|
||
return
|
||
}
|
||
|
||
if (password !== confirmPassword) {
|
||
Toast.show({ title: '两次密码输入不一致' })
|
||
return
|
||
}
|
||
|
||
if (password.length < 6) {
|
||
Toast.show({ title: '密码长度至少6位' })
|
||
return
|
||
}
|
||
|
||
setLoading(true)
|
||
try {
|
||
const result = await signUp.email({
|
||
email,
|
||
username,
|
||
password,
|
||
name: username,
|
||
})
|
||
|
||
console.log('result------------', result);
|
||
|
||
if (result.error) {
|
||
Toast.show({ title: result.error.message || '注册失败' })
|
||
} else {
|
||
Toast.show({ title: '注册成功!' })
|
||
}
|
||
} catch (error: any) {
|
||
Toast.show({ title: error.message || '注册失败' })
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}, [email, username, password, confirmPassword, signUp])
|
||
|
||
const handleSubmit = useCallback(() => {
|
||
if (mode === 'login') {
|
||
handleLogin()
|
||
} else {
|
||
handleRegister()
|
||
}
|
||
}, [mode, handleLogin, handleRegister])
|
||
|
||
return (
|
||
<KeyboardAvoidingView behavior="padding" className="flex-1">
|
||
<Block className="relative flex-1 bg-black">
|
||
<Block className="absolute inset-[0px] z-[0] overflow-hidden">
|
||
<VideoBox url={bgVideo} style={{ position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, opacity: 0.4 }} />
|
||
<Block className="absolute inset-[0px] bg-black/20" />
|
||
</Block>
|
||
|
||
<ScrollView className="relative z-[10] flex-1" contentContainerStyle={{ minHeight: screenHeight }} showsVerticalScrollIndicator={false}>
|
||
<Block className="flex-1 items-center justify-center px-[24px] py-[40px]">
|
||
<Block className="relative w-full max-w-[400px]">
|
||
<Block className="relative mb-[32px] items-center">
|
||
<Block className="h-[80px] w-[80px] items-center justify-center rounded-full border-[4px] border-black bg-accent shadow-deep-black">
|
||
<Ionicons name="flash" size={40} color="black" />
|
||
</Block>
|
||
<Text className="font-900 mt-[16px] text-[32px] italic text-white">LOOMART</Text>
|
||
<Block className="mt-[8px] h-[4px] w-[120px] bg-accent" />
|
||
</Block>
|
||
|
||
<Block className="mb-[24px] flex-row gap-[12px]">
|
||
{(['login', 'register'] as const).map((tabMode) => {
|
||
const isActive = mode === tabMode
|
||
return (
|
||
<Block
|
||
key={tabMode}
|
||
onClick={() => setMode(tabMode)}
|
||
className={`flex-1 items-center justify-center border-[3px] border-black py-[12px] shadow-hard-black ${isActive ? 'bg-accent' : 'bg-white'}`}
|
||
style={{ transform: [{ skewX: '-6deg' }] }}
|
||
>
|
||
<Text
|
||
className={`font-900 text-[14px] italic ${isActive ? 'text-black' : 'text-gray-500'}`}
|
||
style={{ transform: [{ skewX: '6deg' }] }}
|
||
>
|
||
{tabMode === 'login' ? '登录' : '注册'}
|
||
</Text>
|
||
</Block>
|
||
)
|
||
})}
|
||
</Block>
|
||
|
||
<Block className="relative border-[4px] border-black bg-white p-[24px] shadow-deep-black" style={{ transform: [{ skewX: '-3deg' }] }}>
|
||
<Block style={{ transform: [{ skewX: '3deg' }] }}>
|
||
<Block className="mt-[4px] gap-[16px]">
|
||
{mode === 'login' ? (
|
||
<Block>
|
||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">账号</Text>
|
||
<Block className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black" style={{ height: 48 }}>
|
||
<Ionicons name="person-outline" size={20} color="black" style={{ marginRight: 8 }} />
|
||
<TextInput
|
||
value={username}
|
||
onChangeText={setUsername}
|
||
placeholder="用户名"
|
||
placeholderTextColor="#9CA3AF"
|
||
autoCapitalize="none"
|
||
style={{
|
||
flex: 1,
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
color: 'black',
|
||
}}
|
||
/>
|
||
</Block>
|
||
</Block>
|
||
) : (
|
||
<>
|
||
<Block>
|
||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">邮箱</Text>
|
||
<Block className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black" style={{ height: 48 }}>
|
||
<Ionicons name="mail-outline" size={20} color="black" style={{ marginRight: 8 }} />
|
||
<TextInput
|
||
value={email}
|
||
onChangeText={setEmail}
|
||
placeholder="your@email.com"
|
||
placeholderTextColor="#9CA3AF"
|
||
keyboardType="email-address"
|
||
autoCapitalize="none"
|
||
style={{
|
||
flex: 1,
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
color: 'black',
|
||
}}
|
||
/>
|
||
</Block>
|
||
</Block>
|
||
|
||
<Block>
|
||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">用户名</Text>
|
||
<Block className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black" style={{ height: 48 }}>
|
||
<Ionicons name="person-outline" size={20} color="black" style={{ marginRight: 8 }} />
|
||
<TextInput
|
||
value={username}
|
||
onChangeText={setUsername}
|
||
placeholder="username"
|
||
placeholderTextColor="#9CA3AF"
|
||
autoCapitalize="none"
|
||
style={{
|
||
flex: 1,
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
color: 'black',
|
||
}}
|
||
/>
|
||
</Block>
|
||
</Block>
|
||
</>
|
||
)}
|
||
|
||
<Block>
|
||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">密码</Text>
|
||
<Block className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black" style={{ height: 48 }}>
|
||
<Ionicons name="lock-closed-outline" size={20} color="black" style={{ marginRight: 8 }} />
|
||
<TextInput
|
||
value={password}
|
||
onChangeText={setPassword}
|
||
placeholder="••••••••"
|
||
placeholderTextColor="#9CA3AF"
|
||
secureTextEntry
|
||
style={{
|
||
flex: 1,
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
color: 'black',
|
||
}}
|
||
/>
|
||
</Block>
|
||
</Block>
|
||
|
||
{mode === 'register' && (
|
||
<Block>
|
||
<Text className="font-900 mb-[8px] text-[12px] italic text-black">确认密码</Text>
|
||
<Block className="flex-row items-center border-[3px] border-black bg-white px-[12px] shadow-medium-black" style={{ height: 48 }}>
|
||
<Ionicons name="lock-closed-outline" size={20} color="black" style={{ marginRight: 8 }} />
|
||
<TextInput
|
||
value={confirmPassword}
|
||
onChangeText={setConfirmPassword}
|
||
placeholder="••••••••"
|
||
placeholderTextColor="#9CA3AF"
|
||
secureTextEntry
|
||
style={{
|
||
flex: 1,
|
||
fontSize: 14,
|
||
fontWeight: 'bold',
|
||
color: 'black',
|
||
}}
|
||
/>
|
||
</Block>
|
||
</Block>
|
||
)}
|
||
|
||
<Block
|
||
onClick={handleSubmit}
|
||
className={`font-900 mt-[8px] flex-row items-center justify-center gap-[8px] border-[3px] border-black py-[14px] shadow-hard-black ${loading ? 'bg-gray-300' : 'bg-accent'}`}
|
||
>
|
||
{loading ? (
|
||
<Ionicons name="hourglass-outline" size={20} color="black" />
|
||
) : (
|
||
<Ionicons name="flash" size={20} color="black" />
|
||
)}
|
||
<Text className="font-900 text-[16px] italic text-black">{loading ? '处理中...' : mode === 'login' ? '登录' : '注册'}</Text>
|
||
</Block>
|
||
|
||
{mode === 'login' && (
|
||
<Block className="mt-[8px] items-center">
|
||
<Text className="font-700 text-[12px] text-gray-500">忘记密码?点击找回</Text>
|
||
</Block>
|
||
)}
|
||
</Block>
|
||
</Block>
|
||
</Block>
|
||
|
||
<Block className="mt-[24px] items-center">
|
||
<Text className="font-700 text-[12px] text-gray-400">© 2025 LOOMART. All rights reserved.</Text>
|
||
</Block>
|
||
</Block>
|
||
</Block>
|
||
</ScrollView>
|
||
</Block>
|
||
</KeyboardAvoidingView>
|
||
)
|
||
}
|