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
@@ -15,6 +15,10 @@ export const economyManifest = {
|
||||
PERMS.SHOP_VIEW,
|
||||
PERMS.CATALOG_EDIT,
|
||||
PERMS.SHOP_EDIT,
|
||||
PERMS.REFERRALS_VIEW,
|
||||
PERMS.REFERRALS_EDIT,
|
||||
PERMS.DAILY_REWARDS_VIEW,
|
||||
PERMS.DAILY_REWARDS_EDIT,
|
||||
),
|
||||
routes: [],
|
||||
searchProviders: [],
|
||||
|
||||
@@ -621,15 +621,15 @@ describe("housekeeping foundation completion contracts", () => {
|
||||
expect(html).toContain(`>${sentinel}</button>`);
|
||||
});
|
||||
|
||||
it("validates the complete 144-row migration matrix without issues", () => {
|
||||
it("validates the complete 146-row migration matrix without issues", () => {
|
||||
const discovered = discoverLegacyPages();
|
||||
const issues = validateMigrationEntries(
|
||||
discovered,
|
||||
HOUSEKEEPING_MIGRATION_MATRIX,
|
||||
);
|
||||
|
||||
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(144);
|
||||
expect(discovered).toHaveLength(144);
|
||||
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(146);
|
||||
expect(discovered).toHaveLength(146);
|
||||
expect(issues).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -146,6 +146,10 @@ const expectedManifests = [
|
||||
PERMS.SHOP_VIEW,
|
||||
PERMS.CATALOG_EDIT,
|
||||
PERMS.SHOP_EDIT,
|
||||
PERMS.REFERRALS_VIEW,
|
||||
PERMS.REFERRALS_EDIT,
|
||||
PERMS.DAILY_REWARDS_VIEW,
|
||||
PERMS.DAILY_REWARDS_EDIT,
|
||||
],
|
||||
routes: [],
|
||||
},
|
||||
|
||||
@@ -29,12 +29,22 @@ describe("discoverLegacyPages", () => {
|
||||
it("discovers the exact legacy administration inventory", () => {
|
||||
const pages = discoverLegacyPages();
|
||||
|
||||
expect(pages).toHaveLength(144);
|
||||
expect(pages).toHaveLength(146);
|
||||
expect(pages).toContainEqual({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/users/:id/edit",
|
||||
sourceFile: "src/app/admin/users/[id]/edit/page.tsx",
|
||||
});
|
||||
expect(pages).toContainEqual({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/referrals",
|
||||
sourceFile: "src/app/admin/referrals/page.tsx",
|
||||
});
|
||||
expect(pages).toContainEqual({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/daily-rewards",
|
||||
sourceFile: "src/app/admin/daily-rewards/page.tsx",
|
||||
});
|
||||
expect(pages).toContainEqual({
|
||||
surface: "mod",
|
||||
legacyPath: "/mod/cfh/:id",
|
||||
|
||||
@@ -16,6 +16,8 @@ const ECONOMY_PREFIXES = [
|
||||
"/admin/achievements",
|
||||
"/admin/sounds",
|
||||
"/admin/calendar",
|
||||
"/admin/referrals",
|
||||
"/admin/daily-rewards",
|
||||
] as const;
|
||||
|
||||
describe("economyMigrationEntries", () => {
|
||||
|
||||
@@ -419,4 +419,46 @@ export const economyMigrationEntries: readonly MigrationEntry[] = [
|
||||
localization: "COMPLETE",
|
||||
accessibility: "PARTIAL",
|
||||
}),
|
||||
plannedEconomyEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/referrals",
|
||||
sourceFile: "src/app/admin/referrals/page.tsx",
|
||||
targetPath: "/admin/economy/growth/referrals",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.REFERRALS_VIEW],
|
||||
mutate: [PERMS.REFERRALS_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["Referrals", "User", "SiteSettings"],
|
||||
mutations: ["updateReferralSettings"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Attribution tracked at registration via referral code; payouts handled by existing reward cron.",
|
||||
],
|
||||
}),
|
||||
plannedEconomyEntry({
|
||||
surface: "admin",
|
||||
legacyPath: "/admin/daily-rewards",
|
||||
sourceFile: "src/app/admin/daily-rewards/page.tsx",
|
||||
targetPath: "/admin/economy/growth/daily-rewards",
|
||||
decision: "REBUILD",
|
||||
capabilities: {
|
||||
read: [PERMS.DAILY_REWARDS_VIEW],
|
||||
mutate: [PERMS.DAILY_REWARDS_EDIT],
|
||||
},
|
||||
dependencies: {
|
||||
queries: ["WebsiteDailyRewards", "WebsiteDailyRewardClaims"],
|
||||
mutations: ["upsertDailyReward", "deleteDailyReward"],
|
||||
},
|
||||
auditRequirement: "PRIVILEGED_MUTATION",
|
||||
localization: "PARTIAL",
|
||||
accessibility: "PARTIAL",
|
||||
notes: [
|
||||
"Daily login rewards replace the legacy daily duckets script; claims paid via sendCurrency.",
|
||||
],
|
||||
}),
|
||||
];
|
||||
@@ -4,10 +4,10 @@ import { HOUSEKEEPING_MIGRATION_MATRIX } from "./matrix";
|
||||
import { validateMigrationEntries } from "./validate-matrix";
|
||||
|
||||
describe("HOUSEKEEPING_MIGRATION_MATRIX", () => {
|
||||
it("covers all 144 legacy pages exactly once", () => {
|
||||
it("covers all 146 legacy pages exactly once", () => {
|
||||
const discovered = discoverLegacyPages();
|
||||
|
||||
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(144);
|
||||
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(146);
|
||||
expect(
|
||||
validateMigrationEntries(discovered, HOUSEKEEPING_MIGRATION_MATRIX),
|
||||
).toEqual([]);
|
||||
|
||||
Reference in new issue
Block a user