"use client"; import { useState, type FormEvent } from "react"; import { useRouter } from "next/navigation"; import { Bell, Clock3, Gift, Mail, Send, Settings2, ShieldAlert, ShieldCheck } from "lucide-react"; import { Button, buttonVariants } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { saveAppSettings, testSmtpSettings } from "@/actions/admin/settings"; import { toast } from "sonner"; import { getErrorMessage } from "@/lib/errors"; interface AppConfig { siteName: string; siteUrl: string | null; supportContact: string | null; maintenanceNotice: string | null; siteNotice: string | null; allowRegistration: boolean; emailVerificationRequired: boolean; requireInviteCode: boolean; autoReminderDispatchEnabled: boolean; reminderDispatchIntervalMinutes: number; trafficSyncEnabled: boolean; trafficSyncIntervalSeconds: number; inviteRewardEnabled: boolean; inviteRewardRate: number; inviteRewardCouponId: string | null; turnstileSiteKey: string | null; turnstileSecretKey: string | null; smtpEnabled: boolean; smtpHost: string | null; smtpPort: number; smtpSecure: boolean; smtpUser: string | null; smtpFromName: string | null; smtpFromEmail: string | null; } interface CouponOption { id: string; code: string; name: string; } const selectClassName = "premium-input w-full appearance-none px-3.5 py-2 text-sm outline-none"; export function SettingsForm({ config, coupons }: { config: AppConfig; coupons: CouponOption[] }) { const router = useRouter(); const [saving, setSaving] = useState(false); const [testingEmail, setTestingEmail] = useState(false); async function handleSubmit(event: FormEvent) { event.preventDefault(); const form = event.currentTarget; setSaving(true); try { const result = await saveAppSettings(new FormData(form)); if (!result.ok) { toast.error(getErrorMessage(result.error, "保存设置失败")); return; } clearPasswordField(form); router.refresh(); toast.success("设置已保存"); } catch (error) { toast.error(getErrorMessage(error, "保存失败")); } finally { setSaving(false); } } async function handleTestEmail() { const form = document.getElementById("app-settings-form") as HTMLFormElement | null; if (!form) return; setTestingEmail(true); try { const result = await testSmtpSettings(new FormData(form)); if (!result.ok) { if (result.settingsSaved) { clearPasswordField(form); router.refresh(); } toast.error( result.settingsSaved ? `设置已保存,但测试邮件没有发出:${getErrorMessage(result.error, "测试邮件发送失败")}` : getErrorMessage(result.error, "测试邮件发送失败"), ); return; } clearPasswordField(form); router.refresh(); toast.success("设置已保存,测试邮件已发送"); } catch (error) { toast.error(getErrorMessage(error, "测试邮件发送失败")); } finally { setTestingEmail(false); } } function clearPasswordField(form: HTMLFormElement) { const password = form.elements.namedItem("smtpPassword"); if (password instanceof HTMLInputElement) { password.value = ""; } } return (

全局设置

把注册策略、自动化任务和公告内容集中配置,避免页面状态割裂。

基础信息

用于订阅链接、支付回调和 Agent 一键安装命令,反代后建议填写公网域名。

自动化任务

进程级后台定时任务,默认 60 秒;建议不要低于 10 秒。

注册策略

开启后,新用户注册会先收到验证邮件,完成验证后才能登录;关闭后注册成功即可登录。

SMTP 邮件服务

用于注册邮箱验证、忘记密码和账户邮箱变更。密码留空会保留当前配置;测试会先保存当前 SMTP 设置,再发送测试邮件。

邀请奖励

被邀请用户完成首笔订单后,系统会为邀请人记录返利,并可自动把指定优惠券放入邀请人的券包。

Cloudflare Turnstile

为登录和注册页面添加人机验证。留空则不启用。

公告内容