AuthForm.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. 'use client';
  2. import { useState } from 'react';
  3. import api from '@/lib/api';
  4. import { useRouter } from 'next/navigation';
  5. import ForgotPasswordModal from '@/components/ForgotPasswordModal';
  6. import { useLanguage } from '@/lib/i18n/LanguageContext';
  7. import { Zap } from 'lucide-react';
  8. // 1. 引入通用消息弹窗
  9. import MessageModal from '@/components/common/MessageModal';
  10. export default function AuthForm() {
  11. const router = useRouter();
  12. const { t } = useLanguage();
  13. const [isLoginMode, setIsLoginMode] = useState<boolean>(true);
  14. const [loading, setLoading] = useState<boolean>(false);
  15. const [formData, setFormData] = useState({ email: '', password: '' });
  16. const [isForgotOpen, setIsForgotOpen] = useState(false);
  17. // 2. 新增:控制消息弹窗的状态
  18. const [msgModal, setMsgModal] = useState({
  19. isOpen: false,
  20. title: '',
  21. message: '',
  22. type: 'info' as 'info' | 'error' | 'success',
  23. onOk: null as (() => void) | null, // 关闭弹窗后的回调函数
  24. });
  25. // 辅助函数:显示消息弹窗
  26. const showMessage = (message: string, type: 'info' | 'error' | 'success' = 'info', onOk?: () => void) => {
  27. setMsgModal({
  28. isOpen: true,
  29. title: type === 'error' ? t('common.error') : t('common.notice'),
  30. message,
  31. type,
  32. onOk: onOk || null,
  33. });
  34. };
  35. // 处理弹窗关闭
  36. const handleCloseMsg = () => {
  37. const callback = msgModal.onOk;
  38. setMsgModal((prev) => ({ ...prev, isOpen: false }));
  39. if (callback) callback();
  40. };
  41. const handleSubmit = async (e: React.FormEvent) => {
  42. e.preventDefault();
  43. setLoading(true);
  44. try {
  45. let res;
  46. if (isLoginMode) {
  47. res = await api.post('/api/auth/login', formData);
  48. } else {
  49. res = await api.post('/api/auth/auto-register', {});
  50. }
  51. const data = res.data.data || res.data;
  52. const token = data.token || data.access_token;
  53. if (token) {
  54. localStorage.setItem('rsid', token);
  55. if (data.user) {
  56. localStorage.setItem('user_info', JSON.stringify(data.user));
  57. }
  58. window.dispatchEvent(new Event('storage'));
  59. if (!isLoginMode) {
  60. showMessage(t('auth.auto_register_success'), 'success', () => {
  61. router.push('/dashboard');
  62. });
  63. } else {
  64. router.push('/dashboard');
  65. }
  66. } else {
  67. showMessage(t('auth.login_success_no_token'), 'error');
  68. }
  69. } catch (error: any) {
  70. console.error(error);
  71. const msg = error.response?.data?.message || t('common.unknown_error');
  72. showMessage(msg, 'error');
  73. } finally {
  74. setLoading(false);
  75. }
  76. };
  77. return (
  78. <div className="w-full max-w-md p-8 bg-white rounded-xl shadow-xl border border-gray-100">
  79. <h2 className="text-2xl font-bold text-center mb-6">
  80. {isLoginMode ? t('auth.welcome_back') : t('auth.auto_register')}
  81. </h2>
  82. <form onSubmit={handleSubmit} className="space-y-5">
  83. {isLoginMode ? (
  84. <>
  85. <div>
  86. <label className="block text-sm font-medium mb-1 text-gray-700">{t('auth.email_label')}</label>
  87. <input
  88. type="email" required
  89. className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition"
  90. value={formData.email}
  91. onChange={(e) => setFormData({...formData, email: e.target.value})}
  92. placeholder={t('auth.email_placeholder')}
  93. />
  94. </div>
  95. <div>
  96. <div className="flex justify-between items-center mb-1">
  97. <label className="block text-sm font-medium text-gray-700">{t('auth.password_label')}</label>
  98. <button
  99. type="button"
  100. onClick={() => setIsForgotOpen(true)}
  101. className="text-xs text-blue-600 hover:text-blue-800 hover:underline"
  102. >
  103. {t('auth.forgot_password')}
  104. </button>
  105. </div>
  106. <input
  107. type="password" required
  108. className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition"
  109. value={formData.password}
  110. onChange={(e) => setFormData({...formData, password: e.target.value})}
  111. placeholder="••••••••"
  112. />
  113. </div>
  114. </>
  115. ) : (
  116. <div className="bg-blue-50 p-6 rounded-lg text-center border border-blue-100">
  117. <div className="flex justify-center mb-3">
  118. <div className="bg-blue-100 p-3 rounded-full text-blue-600">
  119. <Zap size={24} />
  120. </div>
  121. </div>
  122. <h3 className="font-bold text-blue-900 mb-2">{t('auth.auto_reg_title')}</h3>
  123. <p className="text-sm text-blue-700 leading-relaxed">
  124. {t('auth.auto_reg_desc')}
  125. </p>
  126. </div>
  127. )}
  128. <button
  129. type="submit"
  130. disabled={loading}
  131. className="w-full py-3 bg-blue-600 text-white rounded-lg font-bold hover:bg-blue-700 transition disabled:opacity-50 shadow-md"
  132. >
  133. {loading ? t('common.processing') : (isLoginMode ? t('auth.login_btn') : t('auth.auto_reg_btn_action'))}
  134. </button>
  135. </form>
  136. <div className="mt-6 text-center border-t border-gray-100 pt-4">
  137. <button
  138. type="button"
  139. onClick={() => setIsLoginMode(!isLoginMode)}
  140. className="text-blue-600 text-sm hover:underline"
  141. >
  142. {isLoginMode ? t('auth.no_account') : t('auth.has_account')}
  143. </button>
  144. </div>
  145. <ForgotPasswordModal
  146. isOpen={isForgotOpen}
  147. onClose={() => setIsForgotOpen(false)}
  148. />
  149. {/* 4. 挂载消息弹窗 */}
  150. <MessageModal
  151. isOpen={msgModal.isOpen}
  152. title={msgModal.title}
  153. message={msgModal.message}
  154. type={msgModal.type}
  155. onClose={handleCloseMsg}
  156. />
  157. </div>
  158. );
  159. }