162 lines
4.7 KiB
TypeScript
162 lines
4.7 KiB
TypeScript
import { useState } from 'react'
|
||
import { z } from 'zod'
|
||
import { useForm } from 'react-hook-form'
|
||
import { zodResolver } from '@hookform/resolvers/zod'
|
||
import { Link, useNavigate } from '@tanstack/react-router'
|
||
import { Loader2, LogIn } from 'lucide-react'
|
||
import { toast } from 'sonner'
|
||
import { useAuthStore } from '@/stores/auth-store'
|
||
import { cn } from '@/lib/utils'
|
||
import { Button } from '@/components/ui/button'
|
||
import { supabase } from '@/lib/supabase'
|
||
import {
|
||
Form,
|
||
FormControl,
|
||
FormField,
|
||
FormItem,
|
||
FormLabel,
|
||
FormMessage,
|
||
} from '@/components/ui/form'
|
||
import { Input } from '@/components/ui/input'
|
||
import { PasswordInput } from '@/components/password-input'
|
||
|
||
const formSchema = z.object({
|
||
email: z.email({
|
||
error: (iss) => (iss.input === '' ? 'Please enter your email' : undefined),
|
||
}),
|
||
password: z
|
||
.string()
|
||
.min(1, 'Please enter your password')
|
||
.min(7, 'Password must be at least 7 characters long'),
|
||
})
|
||
|
||
interface UserAuthFormProps extends React.HTMLAttributes<HTMLFormElement> {
|
||
redirectTo?: string
|
||
}
|
||
|
||
export function UserAuthForm({
|
||
className,
|
||
redirectTo,
|
||
...props
|
||
}: UserAuthFormProps) {
|
||
const [isLoading, setIsLoading] = useState(false)
|
||
const navigate = useNavigate()
|
||
const { auth } = useAuthStore()
|
||
|
||
const form = useForm<z.infer<typeof formSchema>>({
|
||
resolver: zodResolver(formSchema),
|
||
defaultValues: {
|
||
email: '',
|
||
password: '',
|
||
},
|
||
})
|
||
|
||
async function onSubmit(data: z.infer<typeof formSchema>) {
|
||
setIsLoading(true)
|
||
|
||
try {
|
||
const { data: authData, error } = await supabase.auth.signInWithPassword({
|
||
email: data.email,
|
||
password: data.password,
|
||
})
|
||
|
||
if (error) throw error
|
||
|
||
if (authData.session && authData.user) {
|
||
setIsLoading(false)
|
||
const user = {
|
||
accountNo: authData.user.id,
|
||
email: authData.user.email || '',
|
||
role: ['admin'],
|
||
exp: authData.session.expires_at ? authData.session.expires_at * 1000 : Date.now() + 24 * 60 * 60 * 1000,
|
||
}
|
||
|
||
auth.setUser(user)
|
||
auth.setAccessToken(authData.session.access_token)
|
||
|
||
const targetPath = redirectTo || '/'
|
||
navigate({ to: targetPath, replace: true })
|
||
toast.success(`Welcome back, ${user.email}!`)
|
||
}
|
||
} catch (error: any) {
|
||
setIsLoading(false)
|
||
toast.error(error.message || 'Login failed')
|
||
}
|
||
}
|
||
|
||
return (
|
||
<Form {...form}>
|
||
<form
|
||
onSubmit={form.handleSubmit(onSubmit)}
|
||
className={cn('grid gap-3', className)}
|
||
{...props}
|
||
>
|
||
<FormField
|
||
control={form.control}
|
||
name='email'
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>邮箱</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder='name@example.com' {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name='password'
|
||
render={({ field }) => (
|
||
<FormItem className='relative'>
|
||
<FormLabel>密码</FormLabel>
|
||
<FormControl>
|
||
<PasswordInput placeholder='********' {...field} />
|
||
</FormControl>
|
||
<FormMessage />
|
||
<Link
|
||
to='/forgot-password'
|
||
className='absolute end-0 -top-0.5 text-sm font-medium text-muted-foreground hover:opacity-75'
|
||
>
|
||
忘记密码?
|
||
</Link>
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<Button className='mt-2' disabled={isLoading}>
|
||
{isLoading ? <Loader2 className='animate-spin' /> : <LogIn />}
|
||
登录
|
||
</Button>
|
||
|
||
<div className='relative my-2'>
|
||
<div className='absolute inset-0 flex items-center'>
|
||
<span className='w-full border-t' />
|
||
</div>
|
||
<div className='relative flex justify-center text-xs uppercase'>
|
||
<span className='bg-background px-2 text-muted-foreground'>
|
||
或使用以下方式
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* <div className='grid grid-cols-3 gap-2'>
|
||
<Button
|
||
variant='outline'
|
||
type='button'
|
||
disabled={isLoading}
|
||
onClick={handleGoogleLogin}
|
||
>
|
||
<IconGoogle className='h-4 w-4' /> Google
|
||
</Button>
|
||
<Button variant='outline' type='button' disabled={isLoading}>
|
||
<IconGithub className='h-4 w-4' /> GitHub
|
||
</Button>
|
||
<Button variant='outline' type='button' disabled={isLoading}>
|
||
<IconFacebook className='h-4 w-4' /> Facebook
|
||
</Button>
|
||
</div> */}
|
||
</form>
|
||
</Form>
|
||
)
|
||
}
|