forked from 77media/video-flow
Merge branch 'feat-active-config' into dev
This commit is contained in:
commit
346cf5233e
@ -1,16 +1,16 @@
|
|||||||
|
|
||||||
# 测试
|
# 测试
|
||||||
NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai
|
# NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai
|
||||||
NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
|
# NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com
|
||||||
NEXT_PUBLIC_CUT_URL = https://77.smartcut.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_GOOGLE_REDIRECT_URI=https://www.movieflow.net/api/auth/google/callback
|
||||||
NEXT_PUBLIC_CUT_URL_TO = https://smartcut.huiying.video
|
# NEXT_PUBLIC_CUT_URL_TO = https://smartcut.huiying.video
|
||||||
# 生产
|
# 生产
|
||||||
# NEXT_PUBLIC_JAVA_URL = https://auth.movieflow.ai
|
NEXT_PUBLIC_JAVA_URL = https://auth.movieflow.ai
|
||||||
# NEXT_PUBLIC_BASE_URL = https://api.video.movieflow.ai
|
NEXT_PUBLIC_BASE_URL = https://api.video.movieflow.ai
|
||||||
# NEXT_PUBLIC_CUT_URL = https://smartcut.api.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_GOOGLE_REDIRECT_URI=https://www.movieflow.ai/api/auth/google/callback
|
||||||
# NEXT_PUBLIC_CUT_URL_TO = https://smartcut.movieflow.ai
|
NEXT_PUBLIC_CUT_URL_TO = https://smartcut.movieflow.ai
|
||||||
# 通用
|
# 通用
|
||||||
# 当前域名配置
|
# 当前域名配置
|
||||||
NEXT_PUBLIC_FRONTEND_URL = https://www.movieflow.ai
|
NEXT_PUBLIC_FRONTEND_URL = https://www.movieflow.ai
|
||||||
|
|||||||
@ -116,3 +116,23 @@ export const fetchTabsByCode = async (code: string): Promise<HomeTabItem[]> => {
|
|||||||
return [];
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -6,8 +6,9 @@ import Link from "next/link";
|
|||||||
import { signInWithGoogle, sendVerificationLink, registerUserWithInvite } from "@/lib/auth";
|
import { signInWithGoogle, sendVerificationLink, registerUserWithInvite } from "@/lib/auth";
|
||||||
import { GradientText } from "@/components/ui/gradient-text";
|
import { GradientText } from "@/components/ui/gradient-text";
|
||||||
import { GoogleLoginButton } from "@/components/ui/google-login-button";
|
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 { isGoogleLoginEnabled } from "@/lib/server-config";
|
||||||
|
import { fetchSettingByCode } from "@/api/serversetting";
|
||||||
|
|
||||||
export default function SignupPage() {
|
export default function SignupPage() {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
@ -28,6 +29,7 @@ export default function SignupPage() {
|
|||||||
const [emailFocused, setEmailFocused] = useState(false);
|
const [emailFocused, setEmailFocused] = useState(false);
|
||||||
const [passwordFocused, setPasswordFocused] = useState(false);
|
const [passwordFocused, setPasswordFocused] = useState(false);
|
||||||
const [showGoogleLogin, setShowGoogleLogin] = useState(false);
|
const [showGoogleLogin, setShowGoogleLogin] = useState(false);
|
||||||
|
const [showRedirectModal, setShowRedirectModal] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// Handle scroll indicator for small screens and load SSO config
|
// Handle scroll indicator for small screens and load SSO config
|
||||||
@ -211,9 +213,19 @@ export default function SignupPage() {
|
|||||||
sessionStorage.removeItem("inviteCode");
|
sessionStorage.removeItem("inviteCode");
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
// Show activation modal instead of redirecting to login
|
// 根据服务端配置是否展示激活弹窗
|
||||||
setShowActivationModal(true);
|
try {
|
||||||
setResendCooldown(60);
|
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) {
|
} catch (error: any) {
|
||||||
console.error("Signup error:", error);
|
console.error("Signup error:", error);
|
||||||
setFormError(error.message || "Registration failed, please try again");
|
setFormError(error.message || "Registration failed, please try again");
|
||||||
@ -636,6 +648,31 @@ export default function SignupPage() {
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
41
lib/store/serverSettingHooks.ts
Normal file
41
lib/store/serverSettingHooks.ts
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
51
lib/store/serverSettingSlice.ts
Normal file
51
lib/store/serverSettingSlice.ts
Normal 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;
|
||||||
|
|
||||||
|
|
||||||
@ -1,9 +1,11 @@
|
|||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
import workflowReducer from './workflowSlice';
|
import workflowReducer from './workflowSlice';
|
||||||
|
import serverSettingReducer from './serverSettingSlice';
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
workflow: workflowReducer,
|
workflow: workflowReducer,
|
||||||
|
serverSetting: serverSettingReducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user