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
This commit is contained in:
1 parent
463bc2cb47
commit
c3ff497050
30 files changed
+2028
-11
No files matched your search
@@ -0,0 +1,57 @@
|
||||
"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");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user