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

+25
View File
@@ -57,8 +57,16 @@ export function RegisterForm({
null,
);
const autoLoginStartedRef = useRef(false);
const [referralCode, setReferralCode] = useState("");
const strength = passwordStrength(password);
// The invite link is `/register?ref=<username>`; read it client-side so the
// attribution travels with the sign-up form without server state.
useEffect(() => {
const ref = new URLSearchParams(window.location.search).get("ref");
if (ref) setReferralCode(ref);
}, []);
// Successful sign-up: sign in with the submitted credentials and go
// straight to /me. If the automatic sign-in is refused (e.g. email
// verification required), fall back to the regular login page.
@@ -187,6 +195,23 @@ export function RegisterForm({
</div>
)}
{referralCode && (
<div
className="animate-fade-in-up flex items-center gap-2 p-3 rounded-lg text-sm font-semibold"
style={{
backgroundColor:
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
color: "var(--color-primary-readable, var(--color-primary))",
border:
"1px solid color-mix(in srgb, var(--color-primary) 22%, transparent)",
}}
>
{t("invitedBy", { username: referralCode })}
</div>
)}
<input type="hidden" name="ref" value={referralCode} />
{/* Username & Email */}
<div className="space-y-2">
<div className="flex items-center gap-2">