返回 AiToEarn
index.tsx
1 /**
2 * LoginDialog - 全局登录弹框组件
3 * 在当前页面弹出登录表单,避免跳转到独立登录页
4 */
5
6 'use client'
7
8 import Image from 'next/image'
9 import Link from 'next/link'
10 import { useRouter } from 'next/navigation'
11 import { memo, useCallback } from 'react'
12 import { useShallow } from 'zustand/shallow'
13
14 import { EmailLoginForm } from '@/app/[lng]/auth/login/components/LoginContent/EmailLoginForm'
15 import { useTransClient } from '@/app/i18n/client'
16 import logo from '@/assets/images/logo.png'
17 import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'
18
19 import { useIsMobile } from '@/hooks/useIsMobile'
20 import { cn } from '@/utils/className'
21 import { useLoginDialogStore } from '@/store/login-dialog'
22
23 export default function LoginDialog() {
24 const { visible } = useLoginDialogStore(
25 useShallow(state => ({ visible: state.visible })),
26 )
27
28 if (!visible)
29 return null
30
31 return <LoginDialogContent />
32 }
33
34 const LoginDialogContent = memo(() => {
35 const router = useRouter()
36 const { t } = useTransClient('login')
37 const isMobile = useIsMobile()
38 const { redirectUrl, inviteCode, fromGuard, closeLoginDialog } = useLoginDialogStore(
39 useShallow(state => ({
40 redirectUrl: state.redirectUrl,
41 inviteCode: state.inviteCode,
42 fromGuard: state.fromGuard,
43 closeLoginDialog: state.closeLoginDialog,
44 })),
45 )
46
47 const handleOpenChange = useCallback((open: boolean) => {
48 if (!open) {
49 closeLoginDialog()
50 if (fromGuard) {
51 router.push('/')
52 }
53 }
54 }, [closeLoginDialog, fromGuard, router])
55
56 const handleLoginSuccess = useCallback(() => {
57 closeLoginDialog()
58 if (fromGuard) {
59 window.location.reload()
60 }
61 else if (redirectUrl) {
62 router.push(redirectUrl)
63 }
64 }, [closeLoginDialog, fromGuard, redirectUrl, router])
65
66 return (
67 <Dialog open onOpenChange={handleOpenChange}>
68 <DialogContent
69 className={cn(
70 isMobile
71 ? 'fixed bottom-0 left-0 right-0 top-auto translate-x-0 translate-y-0 rounded-t-2xl rounded-b-none w-full max-w-none data-[state=open]:slide-in-from-bottom data-[state=closed]:slide-out-to-bottom data-[state=open]:zoom-in-100 data-[state=closed]:zoom-out-100'
72 : 'sm:w-[min(460px,95vw)]',
73 )}
74 >
75 <DialogTitle className="sr-only">{t('welcomeBack')}</DialogTitle>
76
77 {/* Logo + 标题 */}
78 <div className="flex flex-col items-center pb-2 pt-2">
79 <Image
80 src={logo}
81 alt="AiToEarn"
82 width={56}
83 height={56}
84 className="mb-4 drop-shadow-md"
85 />
86 <h2 className="text-xl font-semibold text-foreground">{t('welcomeBack')}</h2>
87 <p className="mt-1.5 text-sm text-muted-foreground">{t('loginSubtitle')}</p>
88 </div>
89
90 {/* 登录表单 */}
91 <div className="px-2">
92 <EmailLoginForm
93 onLoginSuccess={handleLoginSuccess}
94 redirectUrl={redirectUrl}
95 inviteCode={inviteCode}
96 />
97 </div>
98
99 {/* 底部条款 */}
100 <p className="pb-2 text-center text-xs text-muted-foreground/70">
101 {t('termsText')}
102 {' '}
103 <Link
104 href="/websit/terms-of-service"
105 className="text-muted-foreground underline hover:text-foreground"
106 >
107 {t('termsOfService')}
108 </Link>
109 {' '}
110 {t('and')}
111 {' '}
112 <Link
113 href="/websit/privacy-policy"
114 className="text-muted-foreground underline hover:text-foreground"
115 >
116 {t('privacyPolicy')}
117 </Link>
118 </p>
119 </DialogContent>
120 </Dialog>
121 )
122 })
123
124 LoginDialogContent.displayName = 'LoginDialogContent'
125
125 lines Plain Text