Files
EpicNext-Cms/src/actions/admin-referrals.ts
T
openhands c3ff497050
CI / check (push) Successful in 4m25s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m54s
feat(referrals): add referral attribution and daily login rewards
- Track referral attribution at registration via ?ref code with
  same-IP and duplicate-pair guards
- Add daily login rewards with streak tracking, claim flow and
  sendCurrency payout backed by RCON with DB fallback
- Add admin pages for referral settings and the daily reward schedule
- Add migration 0033 with tables, seed schedule, settings and ACL grants
- Add admin.referrals.* and admin.dailyrewards.* permission slugs
- Localize new copy in en, nl and it
2026-09-20 12:29:01 +02:00

58 lines
2.0 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import {
type ActionResult,
actionError,
actionOk,
} from "@/lib/safe-action-shared";
import { logServerError } from "@/lib/server-log";
import { siteSettings } from "@/lib/services/site-settings";
/**
* Referral system configuration for the /admin/referrals settings form. All
* values are validated before the keys leave the CMS settings table, and the
* reward currency must be one of the four emulator wallets (bidirectional:
* the claim action already validates on read).
*/
const VALID_CURRENCIES = ["credits", "duckets", "diamonds", "points"] as const;
export async function updateReferralSettings(input: {
referralsNeeded: string;
rewardAmount: string;
rewardCurrency: string;
blockSameIp: string;
}): Promise<ActionResult> {
await requirePermission(PERMS.REFERRALS_EDIT);
const needed = Number.parseInt(input.referralsNeeded, 10);
const amount = Number.parseInt(input.rewardAmount, 10);
const currency = String(input.rewardCurrency ?? "")
.normalize("NFC")
.trim()
.toLowerCase();
if (!(needed >= 1)) return actionError("Referrals needed must be at least 1");
if (!(amount > 0)) return actionError("Reward amount must be positive");
if (!(VALID_CURRENCIES as readonly string[]).includes(currency))
return actionError("Reward currency is not one of the emulator wallets");
const blockSameIp = input.blockSameIp === "1";
try {
await siteSettings.update("referrals_needed", String(needed));
await siteSettings.update("referral_reward_amount", String(amount));
await siteSettings.update("referral_reward_currency_type", currency);
await siteSettings.update(
"referrals_block_same_ip",
blockSameIp ? "1" : "0",
);
revalidatePath("/admin/referrals");
return actionOk();
} catch (error) {
logServerError("admin.referral_settings_update_failed", error);
return actionError("Referral settings could not be saved");
}
}