forked from 77media/video-flow
提醒用户激活流程
This commit is contained in:
parent
9d730de2ca
commit
c1b55b6827
@ -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
|
||||
@ -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}...`,
|
||||
|
||||
@ -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<string, string> = {
|
||||
"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 (
|
||||
<>
|
||||
<div className="min-h-screen relative overflow-hidden">
|
||||
{/* 背景视频 */}
|
||||
<video
|
||||
@ -374,5 +427,81 @@ export default function SignupPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showActivationModal && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
data-alt="activation-modal-overlay"
|
||||
>
|
||||
<div
|
||||
className="w-full max-w-xl mx-4 relative rounded-2xl border border-white/15 bg-white/5 backdrop-blur-xl shadow-2xl"
|
||||
data-alt="activation-modal"
|
||||
>
|
||||
<div className="absolute -inset-px rounded-2xl bg-gradient-to-br from-cyan-400/10 to-purple-600/10 pointer-events-none"></div>
|
||||
<div className="relative p-6">
|
||||
<div className="flex items-center justify-center mb-4">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-cyan-400 to-purple-600 blur-xl opacity-30"></div>
|
||||
<Mail className="relative z-10 w-10 h-10 text-cyan-300" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white text-center mb-2" data-alt="activation-title">Please verify your email to activate your account</h3>
|
||||
<p className="text-gray-300 text-center mb-4" data-alt="activation-desc">
|
||||
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
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center mb-4 gap-3">
|
||||
{(() => {
|
||||
const provider = getEmailProviderLink(email);
|
||||
if (!provider) return null;
|
||||
const domain = email.split("@")[1] || "email";
|
||||
return (
|
||||
<a
|
||||
href={provider}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-white/15 text-gray-100 hover:bg-white/10 transition-colors"
|
||||
data-alt="email-provider-link"
|
||||
>
|
||||
<Mail className="w-4 h-4" />
|
||||
<span>Open {domain}</span>
|
||||
</a>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{(resendMessage || resendError) && (
|
||||
<div className={`text-sm text-center mb-3 ${resendError ? "text-red-300" : "text-green-300"}`} data-alt="resend-feedback">
|
||||
{resendError || resendMessage}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleResend}
|
||||
disabled={resendCooldown > 0 || resendLoading}
|
||||
className="px-4 py-2 rounded-lg border border-white/15 text-white/90 hover:text-white hover:bg-white/10 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
data-alt="resend-button"
|
||||
>
|
||||
{resendLoading
|
||||
? "Sending..."
|
||||
: resendCooldown > 0
|
||||
? `Resend (${resendCooldown}s)`
|
||||
: "Resend"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.push("/login")}
|
||||
className="px-4 py-2 rounded-lg bg-[#C039F6] hover:bg-[#C039F6]/80 text-white transition-colors"
|
||||
data-alt="go-login-button"
|
||||
>
|
||||
Confirm Activation and Login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -214,10 +214,10 @@ export function TaskInfo({
|
||||
{taskObject?.title ? (
|
||||
<>
|
||||
<span>
|
||||
{taskObject?.title || 'initializing project...'}
|
||||
{taskObject?.title || currentLoadingText}
|
||||
</span>
|
||||
</>
|
||||
) : 'initializing project...'}
|
||||
) : currentLoadingText}
|
||||
</div>
|
||||
|
||||
{/* 主题 彩色标签tags */}
|
||||
|
||||
@ -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<TaskObject>(tempTaskObject.current);
|
||||
const [currentSketchIndex, setCurrentSketchIndex] = useState(0);
|
||||
const [currentLoadingText, setCurrentLoadingText] = useState('initializing project...');
|
||||
const [currentLoadingText, setCurrentLoadingText] = useState<string>(LOADING_TEXT_MAP.getInfo);
|
||||
const [dataLoadError, setDataLoadError] = useState<string | null>(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);
|
||||
|
||||
17
lib/auth.ts
17
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;
|
||||
}
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user