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,6 +1050,7 @@
"emailRequired": "E-mail is verplicht.",
"passwordRequired": "Wachtwoord is verplicht.",
"passwordsDontMatch": "Wachtwoorden komen niet overeen.",
"invitedBy": "Uitgenodigd door {username}",
"accepted": "Accepted & agreed",
"showPassword": "Show",
"hidePassword": "Hide",
@@ -1140,6 +1141,8 @@
"transactions": "Transacties",
"vouchers": "Vouchers",
"subscriptions": "Abonnementen",
"referrals": "Uitnodigingen",
"dailyRewards": "Dagelijkse beloningen",
"rareValues": "Zeldzame waarden",
"badges": "Badges",
"achievements": "Prestaties",
@@ -3061,6 +3064,64 @@
"updated": "Voucher updated",
"updateError": "Could not update voucher"
},
"referrals": {
"title": "Uitnodigingen",
"subtitle": "Uitnodigingssysteem",
"referralsToday": "Uitnodigingen vandaag",
"referralsTotal": "Totaal uitnodigingen",
"invitedTotal": "Getelde uitnodigingen",
"claims": "Beloningen geclaimd",
"settings": "Beloningsinstellingen",
"needed": "Benodigde uitnodigingen",
"rewardAmount": "Beloningshoeveelheid",
"rewardCurrency": "Beloningsvaluta",
"blockSameIp": "Uitnodigingen weigeren die het huidige IP van de uitnodiger delen",
"save": "Instellingen opslaan",
"saving": "Opslaan…",
"saved": "Uitnodigingsinstellingen opgeslagen.",
"saveError": "Uitnodigingsinstellingen konden niet worden opgeslagen.",
"recent": "Recente uitnodigingen",
"unavailable": "Uitnodigingsgegevens zijn tijdelijk niet beschikbaar.",
"empty": "Nog geen uitnodigingen.",
"inviter": "Uitnodiger",
"invitee": "Uitgenodigde gebruiker",
"ip": "IP",
"date": "Datum"
},
"dailyRewards": {
"title": "Dagelijkse beloningen",
"subtitle": "Planning dagelijkse beloningen",
"enabled": "Functiestatus",
"yes": "Ingeschakeld",
"no": "Uitgeschakeld",
"scheduleDays": "Planningsdagen",
"claimsToday": "Claims vandaag",
"claimsTotal": "Totaal claims",
"status": "Functiestatus",
"statusHint": "Wanneer uitgeschakeld, wordt de beloningskaart verborgen en worden nieuwe claims geweigerd.",
"disable": "Uitschakelen",
"enable": "Inschakelen",
"schedule": "Beloningsplanning",
"day": "Dag {day}",
"reward": "Beloning",
"amount": "Hoeveelheid",
"actions": "Acties",
"addDay": "Dag",
"add": "Dag toevoegen",
"save": "Opslaan",
"saving": "Opslaan…",
"saved": "Beloningsplanning opgeslagen.",
"cancel": "Annuleren",
"edit": "Bewerken",
"delete": "Verwijderen",
"created": "Beloningsdag toegevoegd.",
"updated": "Beloningsdag bijgewerkt.",
"deleted": "Beloningsdag verwijderd.",
"saveError": "Beloningsdag kon niet worden opgeslagen.",
"deleteError": "Beloningsdag kon niet worden verwijderd.",
"confirmDeleteTitle": "Beloningsdag verwijderen",
"confirmDeleteBody": "Deze beloningsdag wordt niet langer toegekend. Eerdere claims blijven vastgelegd."
},
"subscriptions": {
"title": "Abonnementen",
"subtitle": "Actieve terugkerende abonnementen",
@@ -4688,6 +4749,8 @@
"vouchers": "Vouchers",
"subscriptions": "Abonnementen",
"calendar": "Kalender",
"referrals": "Uitnodigingen",
"dailyRewards": "Dagelijkse beloningen",
"history": "Geschiedenis",
"moderationTab": "Moderatie",
"monitoring": "Bewaking",
@@ -6172,6 +6235,7 @@
"friends": "{count, plural, one {# vriendschapsverzoek om te bekijken} other {# vriendschapsverzoeken om te bekijken}}",
"event": "Je volgende aangemelde evenement: {title} · {date}",
"reward": "Je beloning voor uitnodigingen staat klaar",
"dailyReward": "Je dagelijkse beloning staat klaar",
"empty": "Alles is bijgewerkt. Er zijn geen acties nodig.",
"unavailable": "Sommige acties konden niet worden gecontroleerd. Beschikbare acties staan hierboven.",
"retry": "Opnieuw proberen",
@@ -6183,6 +6247,7 @@
"login": "Sign in",
"never": "Never",
"claimed": "Reward claimed successfully!",
"dailyClaimed": "Dagelijkse beloning geclaimd — tot morgen!",
"welcome": "Your space on {hotel}",
"noMotto": "No motto yet",
"registered": "Registered: {date}",
@@ -6234,7 +6299,24 @@
"bad_config": "The reward is not configured correctly. Contact the staff.",
"ratelimit": "Too many attempts. Wait a moment and try again.",
"error": "The reward could not be claimed. Please try again."
}
},
"dailyErrors": {
"already_claimed": "Je hebt de dagelijkse beloning van vandaag al geclaimd.",
"disabled": "Dagelijkse beloningen zijn momenteel uitgeschakeld.",
"bad_config": "De planning voor dagelijkse beloningen is niet correct ingesteld. Neem contact op met het personeel.",
"ratelimit": "Te veel pogingen. Wacht even en probeer het opnieuw.",
"error": "De dagelijkse beloning kon niet worden geclaimd. Probeer het opnieuw."
},
"dailyRewardTitle": "Dagelijkse beloningen",
"dailyRewardHint": "Log elke dag in en claim je beloning — je streak groeit tot de grote dagen.",
"dailyRewardDay": "Dag {day}",
"dailyRewardStreak": "{count, plural, one {Streak: # dag} other {Streak: # dagen}}",
"dailyRewardNextClaim": "Volgende claim: {amount} {currency}",
"dailyRewardTomorrow": "Morgen: {amount} {currency}",
"dailyRewardClaim": "Dagelijkse beloning claimen",
"dailyRewardClaimedToday": "Vandaag geclaimd — kom morgen terug voor de volgende beloning.",
"dailyRewardDisabled": "Dagelijkse beloningen zijn momenteel uitgeschakeld. Je kunt nog steeds het hotel verkennen.",
"dailyRewardUnavailable": "De planning voor dagelijkse beloningen kon niet worden geladen. Probeer het opnieuw."
},
"search": {
"title": "Zoeken",