forked from 77media/video-flow
新增 通过配置控制激活邮件弹窗展示
This commit is contained in:
parent
cc1fdc5a40
commit
61795d0ddb
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 workflowReducer from './workflowSlice';
|
||||
import serverSettingReducer from './serverSettingSlice';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
workflow: workflowReducer,
|
||||
serverSetting: serverSettingReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user