feat(referrals): add referral attribution and daily login rewards
CI / check (push) Successful in 4m25s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 2m54s

- 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:
openhands committed 2026-09-20 12:29:01 +02:00
1 parent 463bc2cb47
commit c3ff497050
30 files changed
+2028 -11

No files matched your search

+83 -1
View File
@@ -1050,7 +1050,8 @@
"usernameRequired": "Username is required.",
"emailRequired": "Email is required.",
"passwordRequired": "Password is required.",
"passwordsDontMatch": "Passwords do not match."
"passwordsDontMatch": "Passwords do not match.",
"invitedBy": "Invited by {username}"
},
"forgot": {
"title": "Reset password",
@@ -1143,6 +1144,8 @@
"transactions": "Transactions",
"vouchers": "Vouchers",
"subscriptions": "Subscriptions",
"referrals": "Referrals",
"dailyRewards": "Daily rewards",
"rareValues": "Rare values",
"badges": "Badges",
"achievements": "Achievements",
@@ -3197,6 +3200,64 @@
"cancel": "Cancel"
}
},
"referrals": {
"title": "Referrals",
"subtitle": "Invitation system",
"referralsToday": "Referrals today",
"referralsTotal": "Total referrals",
"invitedTotal": "Invitations counted",
"claims": "Rewards claimed",
"settings": "Reward settings",
"needed": "Referrals needed",
"rewardAmount": "Reward amount",
"rewardCurrency": "Reward currency",
"blockSameIp": "Reject referrals sharing the inviter's current IP",
"save": "Save settings",
"saving": "Saving…",
"saved": "Referral settings saved.",
"saveError": "Referral settings could not be saved.",
"recent": "Recent referrals",
"unavailable": "Referral data is temporarily unavailable.",
"empty": "No referrals yet.",
"inviter": "Inviter",
"invitee": "Invited user",
"ip": "IP",
"date": "Date"
},
"dailyRewards": {
"title": "Daily login rewards",
"subtitle": "Daily login reward schedule",
"enabled": "Feature status",
"yes": "Enabled",
"no": "Disabled",
"scheduleDays": "Schedule days",
"claimsToday": "Claims today",
"claimsTotal": "Total claims",
"status": "Feature status",
"statusHint": "When disabled, the reward card is hidden and new claims are rejected.",
"disable": "Disable",
"enable": "Enable",
"schedule": "Reward schedule",
"day": "Day {day}",
"reward": "Reward",
"amount": "Amount",
"actions": "Actions",
"addDay": "Day",
"add": "Add day",
"save": "Save",
"saving": "Saving…",
"saved": "Reward schedule saved.",
"cancel": "Cancel",
"edit": "Edit",
"delete": "Delete",
"created": "Reward day added.",
"updated": "Reward day updated.",
"deleted": "Reward day removed.",
"saveError": "Reward day could not be saved.",
"deleteError": "Reward day could not be removed.",
"confirmDeleteTitle": "Remove reward day",
"confirmDeleteBody": "This reward day will no longer be granted. Past claims stay recorded."
},
"subscriptions": {
"title": "Subscriptions",
"subtitle": "Active recurring subscriptions",
@@ -4056,6 +4117,8 @@
"vouchers": "Vouchers",
"subscriptions": "Subscriptions",
"calendar": "Calendar",
"referrals": "Referrals",
"dailyRewards": "Daily rewards",
"history": "History",
"moderationTab": "Moderation",
"monitoring": "Monitoring",
@@ -6277,6 +6340,7 @@
"login": "Sign in",
"never": "Never",
"claimed": "Reward claimed successfully!",
"dailyClaimed": "Daily reward claimed — see you tomorrow!",
"welcome": "Your space on {hotel}",
"noMotto": "No motto yet",
"registered": "Registered: {date}",
@@ -6329,6 +6393,23 @@
"ratelimit": "Too many attempts. Wait a moment and try again.",
"error": "The reward could not be claimed. Please try again."
},
"dailyErrors": {
"already_claimed": "You already claimed today's daily reward.",
"disabled": "Daily rewards are currently disabled.",
"bad_config": "The daily reward schedule is not configured correctly. Contact the staff.",
"ratelimit": "Too many attempts. Wait a moment and try again.",
"error": "The daily reward could not be claimed. Please try again."
},
"dailyRewardTitle": "Daily rewards",
"dailyRewardHint": "Log in every day and claim your reward — streaks grow until the big days.",
"dailyRewardDay": "Day {day}",
"dailyRewardStreak": "{count, plural, one {Streak: # day} other {Streak: # days}}",
"dailyRewardNextClaim": "Next claim: {amount} {currency}",
"dailyRewardTomorrow": "Tomorrow: {amount} {currency}",
"dailyRewardClaim": "Claim daily reward",
"dailyRewardClaimedToday": "Claimed today — come back tomorrow for the next reward.",
"dailyRewardDisabled": "Daily rewards are currently disabled. You can still explore the hotel.",
"dailyRewardUnavailable": "The daily reward schedule could not be loaded. Please try again.",
"messagesCaughtUp": "No unread messages",
"messagesUnavailable": "Open your inbox to check",
"eventTitle": "On the calendar",
@@ -6345,6 +6426,7 @@
"friends": "{count, plural, one {# friend request to review} other {# friend requests to review}}",
"event": "Your next registered event: {title} · {date}",
"reward": "Your referral reward is ready to claim",
"dailyReward": "Your daily reward is ready to claim",
"empty": "You're all caught up. No actions waiting.",
"unavailable": "Some actions could not be checked. Available actions are shown above.",
"retry": "Try again",