Merge branch 'feat-active-config' into dev

This commit is contained in:
moux1024 2025-09-29 14:54:47 +08:00
commit 346cf5233e
6 changed files with 165 additions and 14 deletions

View File

@ -1,16 +1,16 @@
# 测试
NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai
NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com
NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.net/api/auth/google/callback
NEXT_PUBLIC_CUT_URL_TO = https://smartcut.huiying.video
# NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai
# NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
# NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com
# NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.net/api/auth/google/callback
# NEXT_PUBLIC_CUT_URL_TO = 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.api.movieflow.ai
# NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.ai/api/auth/google/callback
# NEXT_PUBLIC_CUT_URL_TO = https://smartcut.movieflow.ai
NEXT_PUBLIC_JAVA_URL = https://auth.movieflow.ai
NEXT_PUBLIC_BASE_URL = https://api.video.movieflow.ai
NEXT_PUBLIC_CUT_URL = https://smartcut.api.movieflow.ai
NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.ai/api/auth/google/callback
NEXT_PUBLIC_CUT_URL_TO = https://smartcut.movieflow.ai
# 通用
# 当前域名配置
NEXT_PUBLIC_FRONTEND_URL = https://www.movieflow.ai

View File

@ -116,3 +116,23 @@ export const fetchTabsByCode = async (code: string): Promise<HomeTabItem[]> => {
return [];
}
};
/**
* code
* res.data.value(JSON )
*/
export const fetchSettingByCode = async <T = unknown>(
code: string,
defaultValue?: T
): Promise<T | undefined> => {
try {
const res = await post<any>(`/api/server-setting/find_by_code`, { code });
if (!res || res.code !== 0 || !res.successful || !res.data) return defaultValue;
const raw = res.data.value;
if (typeof raw !== 'string' || raw.length === 0) return defaultValue;
const parsed: T = JSON.parse(raw);
return parsed;
} catch {
return defaultValue;
}
};

View File

@ -6,8 +6,9 @@ import Link from "next/link";
import { signInWithGoogle, sendVerificationLink, registerUserWithInvite } from "@/lib/auth";
import { GradientText } from "@/components/ui/gradient-text";
import { GoogleLoginButton } from "@/components/ui/google-login-button";
import { Eye, EyeOff, Mail } from "lucide-react";
import { Eye, EyeOff, Mail, PartyPopper } from "lucide-react";
import { isGoogleLoginEnabled } from "@/lib/server-config";
import { fetchSettingByCode } from "@/api/serversetting";
export default function SignupPage() {
const [name, setName] = useState("");
@ -28,6 +29,7 @@ export default function SignupPage() {
const [emailFocused, setEmailFocused] = useState(false);
const [passwordFocused, setPasswordFocused] = useState(false);
const [showGoogleLogin, setShowGoogleLogin] = useState(false);
const [showRedirectModal, setShowRedirectModal] = useState(false);
const router = useRouter();
// Handle scroll indicator for small screens and load SSO config
@ -211,9 +213,19 @@ export default function SignupPage() {
sessionStorage.removeItem("inviteCode");
} catch {}
// Show activation modal instead of redirecting to login
setShowActivationModal(true);
setResendCooldown(60);
// 根据服务端配置是否展示激活弹窗
try {
const showActivation = await fetchSettingByCode<{show: boolean}>("showActivation", {show: true});
if (showActivation?.show === true) {
setShowActivationModal(true);
setResendCooldown(60);
} else {
setShowRedirectModal(true);
setTimeout(() => {
router.push("/login");
}, 1500);
}
} catch {}
} catch (error: any) {
console.error("Signup error:", error);
setFormError(error.message || "Registration failed, please try again");
@ -636,6 +648,31 @@ export default function SignupPage() {
</div>
</div>
)}
{showRedirectModal && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
data-alt="redirect-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="redirect-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>
<PartyPopper 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="redirect-title">,</h3>
<p className="text-gray-300 text-center mb-2" data-alt="redirect-desc">
Please wait a moment, redirecting to login...
</p>
</div>
</div>
</div>
)}
</>
);
}

View File

@ -0,0 +1,41 @@
import { useCallback, useEffect } from 'react';
import { useAppDispatch, useAppSelector } from './hooks';
import { setError, setLoading, setValue } from './serverSettingSlice';
import { fetchSettingByCode } from '@/api/serversetting';
/**
* 使 code
* - store
* - loading/error/value
*/
export function useServerSetting<T = unknown>(code: string, defaultValue?: T) {
const dispatch = useAppDispatch();
const entry = useAppSelector((s) => s.serverSetting.byCode[code]);
const load = useCallback(async () => {
if (!code) return;
dispatch(setLoading({ code, loading: true }));
try {
const value = await fetchSettingByCode<T>(code, defaultValue as T | undefined);
dispatch(setValue<T>({ code, value: value as T }));
} catch (e: any) {
dispatch(setError({ code, error: e?.message || 'Load failed' }));
}
}, [code, defaultValue, dispatch]);
useEffect(() => {
// 仅当未加载过或无值时加载
if (!entry || (entry && entry.value === undefined && !entry.loading)) {
load();
}
}, [entry, load]);
return {
loading: entry?.loading ?? false,
error: entry?.error,
value: (entry?.value as T | undefined) ?? defaultValue,
reload: load,
} as const;
}

View File

@ -0,0 +1,51 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface ServerSettingEntry<T = unknown> {
/** 是否加载中 */
loading: boolean;
/** 错误信息(可选) */
error?: string;
/** 解析后的配置值 */
value?: T;
}
export interface ServerSettingState {
/** 以 code 为 key 存放配置 */
byCode: Record<string, ServerSettingEntry<any>>;
}
const initialState: ServerSettingState = {
byCode: {},
};
export const serverSettingSlice = createSlice({
name: 'serverSetting',
initialState,
reducers: {
setLoading(state, action: PayloadAction<{ code: string; loading: boolean }>) {
const { code, loading } = action.payload;
const current = state.byCode[code] || {};
state.byCode[code] = { ...current, loading, error: loading ? undefined : current.error };
},
setValue<T>(state, action: PayloadAction<{ code: string; value: T }>) {
const { code, value } = action.payload as { code: string; value: unknown };
const current = state.byCode[code] || {};
state.byCode[code] = { ...current, loading: false, error: undefined, value };
},
setError(state, action: PayloadAction<{ code: string; error: string }>) {
const { code, error } = action.payload;
const current = state.byCode[code] || {};
state.byCode[code] = { ...current, loading: false, error };
},
clear(state, action: PayloadAction<{ code: string }>) {
const { code } = action.payload;
delete state.byCode[code];
},
},
});
export const { setLoading, setValue, setError, clear } = serverSettingSlice.actions;
export default serverSettingSlice.reducer;

View File

@ -1,9 +1,11 @@
import { configureStore } from '@reduxjs/toolkit';
import workflowReducer from './workflowSlice';
import serverSettingReducer from './serverSettingSlice';
export const store = configureStore({
reducer: {
workflow: workflowReducer,
serverSetting: serverSettingReducer,
},
});