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
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,72 @@
|
||||
import { asc, sql } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
import { db, WebsiteDailyRewardClaims, WebsiteDailyRewards } from "@/lib/db";
|
||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
import {
|
||||
type DailyRewardEditRow,
|
||||
DailyRewardsClient,
|
||||
} from "./daily-rewards-client";
|
||||
|
||||
export default async function AdminDailyRewards() {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
if (!canAccess(permissions, PERMS.DAILY_REWARDS_VIEW, session.user.rank)) {
|
||||
redirect("/admin");
|
||||
}
|
||||
const canEdit = canAccess(
|
||||
permissions,
|
||||
PERMS.DAILY_REWARDS_EDIT,
|
||||
session.user.rank,
|
||||
);
|
||||
|
||||
const enabled = await siteSettings.getBool("daily_reward_enabled", true);
|
||||
|
||||
let rows: DailyRewardEditRow[] = [];
|
||||
let claimsToday: number | null = null;
|
||||
let dayTotal: number | null = null;
|
||||
try {
|
||||
const schedule = await db
|
||||
.select({
|
||||
id: WebsiteDailyRewards.id,
|
||||
day: WebsiteDailyRewards.day,
|
||||
currency: WebsiteDailyRewards.currency,
|
||||
amount: WebsiteDailyRewards.amount,
|
||||
})
|
||||
.from(WebsiteDailyRewards)
|
||||
.orderBy(asc(WebsiteDailyRewards.day));
|
||||
rows = schedule.map((row) => ({
|
||||
id: String(row.id),
|
||||
day: Number(row.day),
|
||||
currency: row.currency,
|
||||
amount: Number(row.amount),
|
||||
}));
|
||||
|
||||
const [[claimed], [total]] = await Promise.all([
|
||||
db
|
||||
.select({
|
||||
value: sql<number>`COUNT(*)`,
|
||||
})
|
||||
.from(WebsiteDailyRewardClaims)
|
||||
.where(sql`${WebsiteDailyRewardClaims.claimDate} = CURRENT_DATE`),
|
||||
db
|
||||
.select({ value: sql<number>`COUNT(*)` })
|
||||
.from(WebsiteDailyRewardClaims),
|
||||
]);
|
||||
claimsToday = Number(claimed?.value ?? 0);
|
||||
dayTotal = Number(total?.value ?? 0);
|
||||
} catch {
|
||||
rows = [];
|
||||
claimsToday = null;
|
||||
dayTotal = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DailyRewardsClient
|
||||
rows={rows}
|
||||
enabled={enabled}
|
||||
canEdit={canEdit}
|
||||
claimsToday={claimsToday}
|
||||
dayTotal={dayTotal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user