diff --git a/.env.development b/.env.development index 36482e6..5061f56 100644 --- a/.env.development +++ b/.env.development @@ -1,10 +1,8 @@ NEXT_PUBLIC_JAVA_URL = https://77.app.java.auth.qikongjian.com +# NEXT_PUBLIC_JAVA_URL = http://192.168.120.83:8080 NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com NEXT_PUBLIC_CUT_URL = https://smartcut.huiying.video -# NEXT_PUBLIC_JAVA_URL = https://auth.movieflow.ai -# NEXT_PUBLIC_BASE_URL = https://api.video.movieflow.ai -# NEXT_PUBLIC_CUT_URL = https://smartcut.movieflow.ai # 失败率 NEXT_PUBLIC_ERROR_CONFIG = 0.1 \ No newline at end of file diff --git a/api/DTO/movieEdit.ts b/api/DTO/movieEdit.ts index 9a457f4..b0f4961 100644 --- a/api/DTO/movieEdit.ts +++ b/api/DTO/movieEdit.ts @@ -656,7 +656,8 @@ export interface ShotVideo { // 执行loading文字映射 export const LOADING_TEXT_MAP = { - initializing: 'initializing...', + getInfo: 'Getting information...', + initializing: 'initializing project...', script: 'Generating script...', getSketchStatus: 'Getting sketch status...', sketch: (count: number, total: number) => `Generating sketch ${count}/${total}...`, diff --git a/app/signup/page.tsx b/app/signup/page.tsx index 247756f..f36f14c 100644 --- a/app/signup/page.tsx +++ b/app/signup/page.tsx @@ -3,9 +3,9 @@ import React, { useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; -import { signInWithGoogle, registerUser } from "@/lib/auth"; +import { signInWithGoogle, registerUser, sendVerificationLink } from "@/lib/auth"; import { GradientText } from "@/components/ui/gradient-text"; -import { Eye, EyeOff } from "lucide-react"; +import { Eye, EyeOff, Mail } from "lucide-react"; export default function SignupPage() { const [name, setName] = useState(""); @@ -20,6 +20,11 @@ export default function SignupPage() { const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [agreeToTerms, setAgreeToTerms] = useState(true); + const [showActivationModal, setShowActivationModal] = useState(false); + const [resendCooldown, setResendCooldown] = useState(60); + const [resendLoading, setResendLoading] = useState(false); + const [resendMessage, setResendMessage] = useState(""); + const [resendError, setResendError] = useState(""); const router = useRouter(); /** Password validation function with English prompts */ @@ -36,6 +41,52 @@ export default function SignupPage() { return ""; }; + /** Get quick link to email provider by domain */ + const getEmailProviderLink = (addr: string): string | undefined => { + const domain = addr.split("@")[1]?.toLowerCase(); + if (!domain) return undefined; + const map: Record = { + "gmail.com": "https://mail.google.com", + "outlook.com": "https://outlook.live.com/mail", + "hotmail.com": "https://outlook.live.com/mail", + "live.com": "https://outlook.live.com/mail", + "yahoo.com": "https://mail.yahoo.com", + "icloud.com": "https://www.icloud.com/mail", + "qq.com": "https://mail.qq.com", + "163.com": "https://mail.163.com", + "126.com": "https://mail.126.com", + "yeah.net": "https://mail.yeah.net", + }; + return map[domain]; + }; + + /** Handle resend activation email */ + const handleResend = async () => { + if (resendCooldown > 0 || resendLoading) return; + try { + setResendLoading(true); + setResendMessage(""); + setResendError(""); + await sendVerificationLink(email); + setResendMessage("Resent. Please check your email."); + setResendCooldown(60); + } catch (err: any) { + setResendError(err?.message || "Sending failed, please try again later"); + } finally { + setResendLoading(false); + } + }; + + /** Countdown for resend button */ + React.useEffect(() => { + if (!showActivationModal) return; + if (resendCooldown <= 0) return; + const timer = setInterval(() => { + setResendCooldown((s) => (s > 0 ? s - 1 : 0)); + }, 1000); + return () => clearInterval(timer); + }, [showActivationModal, resendCooldown]); + /** Handle Terms of Service click */ const handleTermsClick = () => { window.open("/Terms", "_blank"); @@ -120,8 +171,9 @@ export default function SignupPage() { inviteCode: inviteCode || undefined, }); - // Redirect to login page after successful registration - router.push("/login?registered=true"); + // Show activation modal instead of redirecting to login + setShowActivationModal(true); + setResendCooldown(60); } catch (error: any) { console.error("Signup error:", error); setFormError(error.message||error.msg || "Registration failed, please try again"); @@ -131,6 +183,7 @@ export default function SignupPage() { }; return ( + <>
{/* 背景视频 */}
+ {showActivationModal && ( +
+
+
+
+
+
+
+ +
+
+

Please verify your email to activate your account

+

+ We have sent an activation email to {email || "your email"}; if you don't receive it, please check your spam folder or try again later +

+ +
+ {(() => { + const provider = getEmailProviderLink(email); + if (!provider) return null; + const domain = email.split("@")[1] || "email"; + return ( + + + Open {domain} + + ); + })()} +
+ + {(resendMessage || resendError) && ( +
+ {resendError || resendMessage} +
+ )} + +
+ + +
+
+
+
+ )} + ); } diff --git a/components/pages/work-flow/task-info.tsx b/components/pages/work-flow/task-info.tsx index 361d836..8f61632 100644 --- a/components/pages/work-flow/task-info.tsx +++ b/components/pages/work-flow/task-info.tsx @@ -214,10 +214,10 @@ export function TaskInfo({ {taskObject?.title ? ( <> - {taskObject?.title || 'initializing project...'} + {taskObject?.title || currentLoadingText} - ) : 'initializing project...'} + ) : currentLoadingText} {/* 主题 彩色标签tags */} diff --git a/components/pages/work-flow/use-workflow-data.tsx b/components/pages/work-flow/use-workflow-data.tsx index 95c282c..03d46b4 100644 --- a/components/pages/work-flow/use-workflow-data.tsx +++ b/components/pages/work-flow/use-workflow-data.tsx @@ -63,14 +63,14 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa note: '' } }); - let loadingText: any = useRef(LOADING_TEXT_MAP.initializing); + let loadingText: any = useRef(LOADING_TEXT_MAP.getInfo); const errorConfig = Number(process.env.NEXT_PUBLIC_ERROR_CONFIG); // 更新 taskObject 的类型 const [taskObject, setTaskObject] = useState(tempTaskObject.current); const [currentSketchIndex, setCurrentSketchIndex] = useState(0); - const [currentLoadingText, setCurrentLoadingText] = useState('initializing project...'); + const [currentLoadingText, setCurrentLoadingText] = useState(LOADING_TEXT_MAP.getInfo); const [dataLoadError, setDataLoadError] = useState(null); const [needStreamData, setNeedStreamData] = useState(false); const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false); @@ -535,7 +535,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa originalText: '', isLoading: true }); - setCurrentLoadingText('initializing project...'); + setCurrentLoadingText(LOADING_TEXT_MAP.getInfo); // 获取剧集详情 const response = await detailScriptEpisodeNew({ project_id: episodeId }); @@ -554,6 +554,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa // 设置标题 if (!name) { + setCurrentLoadingText(LOADING_TEXT_MAP.initializing); // 如果没有标题,轮询获取 const titleResponse = await getScriptTitle({ project_id: episodeId }); console.log('titleResponse', titleResponse); diff --git a/lib/auth.ts b/lib/auth.ts index fb3de7c..a4c5229 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -369,3 +369,20 @@ export const registerUser = async ({ throw error; } }; + + +export const sendVerificationLink = async (email: string) => { + try { + const response = await fetch(`${JAVA_BASE_URL}/api/user/sendVerificationLink?email=${email}`); + const data = await response.json(); + if(!data.success){ + throw new Error(data.message||data.msg) + } + return data as { + success: boolean; + }; + } catch (error) { + console.error('Send verification link failed:', error); + throw error; + } +}; \ No newline at end of file