Files
Epicnabbo-Catalogus-Updated…/src/app/(site)/me/DailyRewardCard.tsx
T
openhands c3ff497050 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
2026-09-20 12:29:01 +02:00

95 lines
2.5 KiB
TypeScript

import { getTranslations } from "next-intl/server";
import { claimDailyReward } from "@/actions/daily-reward";
import { CurrencyIcon } from "@/components/shared/currency-icon";
import { SurfaceCard } from "@/components/surface-card";
import {
type DailyRewardState,
rewardForStreak,
} from "@/lib/services/daily-rewards";
import styles from "./dashboard.module.css";
export default async function DailyRewardCard({
state,
}: {
state: DailyRewardState;
}) {
const t = await getTranslations("pages.myDashboard");
if (!state.enabled) {
return (
<SurfaceCard title={t("dailyRewardTitle")} bodyClassName="p-5">
<p className="muted" role="status">
{t("dailyRewardDisabled")}
</p>
</SurfaceCard>
);
}
if (state.rewards.length === 0) {
return (
<SurfaceCard title={t("dailyRewardTitle")} bodyClassName="p-5">
<p className="muted" role="status">
{t("dailyRewardUnavailable")}
</p>
</SurfaceCard>
);
}
const upcoming = state.alreadyClaimedToday
? rewardForStreak(state.rewards, state.nextStreak + 1)
: state.nextReward;
return (
<SurfaceCard title={t("dailyRewardTitle")} bodyClassName="p-5">
<div id="daily-reward" />
<p>{t("dailyRewardHint")}</p>
<ul className={styles.dailyTrack}>
{state.rewards.map((reward) => (
<li
key={reward.id}
className={`${styles.dailyCell}${
!state.alreadyClaimedToday && upcoming?.day === reward.day
? ` ${styles.dailyCellNext}`
: ""
}`}
>
<span>{t("dailyRewardDay", { day: reward.day })}</span>
<CurrencyIcon kind={reward.currency} size={20} alt="" />
<strong>{reward.amount}</strong>
</li>
))}
</ul>
<p className={styles.dailyStreak}>
{t("dailyRewardStreak", { count: state.nextStreak })}
</p>
{upcoming && (
<p className={styles.reward}>
<CurrencyIcon kind={upcoming.currency} size={20} alt="" />
{t(
state.alreadyClaimedToday
? "dailyRewardTomorrow"
: "dailyRewardNextClaim",
{
amount: upcoming.amount,
currency: t.has(`currencies.${upcoming.currency}`)
? t(`currencies.${upcoming.currency}`)
: upcoming.currency,
},
)}
</p>
)}
{state.alreadyClaimedToday ? (
<p className="muted" role="status">
{t("dailyRewardClaimedToday")}
</p>
) : (
<form action={claimDailyReward} className={styles.claim}>
<button type="submit" className="btn btn-primary">
{t("dailyRewardClaim")}
</button>
</form>
)}
</SurfaceCard>
);
}