import type { CSSProperties } from "react"; import Link from "next/link"; import { redirect } from "next/navigation"; import CopyReferralButton from "./CopyReferralButton"; import { ContentCard, EmptyState, OnlineBadge, StatBlock } from "@/components/public/ui"; import { auth } from "@/lib/auth"; import { avatarImageUrl } from "@/lib/format"; import { prisma } from "@/lib/prisma"; import { siteSettings } from "@/lib/services/site-settings"; import { claimReferral } from "@/actions/referral"; export const dynamic = "force-dynamic"; export const metadata = { title: "My dashboard" }; type SearchParams = Promise<{ claimed?: string; error?: string }>; /** Inline-styled feedback banner using the theme's CSS variables. */ function feedbackStyle(tone: "success" | "error" | "warning"): CSSProperties { const accent = tone === "error" ? "var(--color-danger)" : tone === "warning" ? "var(--color-accent)" : "var(--color-primary)"; return { margin: 0, padding: "0.85rem 1rem", borderRadius: "var(--radius-md)", border: `1px solid ${accent}`, color: "var(--color-text-readable, var(--color-text))", fontSize: "0.9rem", fontWeight: 600, background: "var(--color-surface)", borderLeft: `4px solid ${accent}`, }; } const ERROR_MESSAGES: Record = { not_enough: "You do not have enough referrals to claim your reward yet.", no_referrals: "You haven't referred anyone yet — share your link to get started.", bad_config: "The referral reward is not configured correctly. Please contact a staff member.", error: "Something went wrong while claiming your reward. Please try again.", }; function getErrorMessage(error: string): string { if (error === "not_enough") return ERROR_MESSAGES.not_enough; if (error === "no_referrals") return ERROR_MESSAGES.no_referrals; if (error === "bad_config") return ERROR_MESSAGES.bad_config; return ERROR_MESSAGES.error; } export default async function MePage({ searchParams }: { searchParams: SearchParams }) { const session = await auth(); if (!session?.user?.id) redirect("/login"); const userId = Number(session.user.id); if (!Number.isFinite(userId) || userId <= 0) redirect("/login"); const { claimed, error } = await searchParams; let content; try { // --- Account + reward config ----------------------------------------- const [user, imagerBase, neededRaw, amountRaw, currencyRaw] = await Promise.all([ prisma.user.findUnique({ where: { id: userId }, select: { id: true, username: true, look: true, motto: true, rank: true, credits: true, accountCreated: true, }, }), siteSettings.get("habbo_imaging_url", "https://www.habbo.com/habbo-imaging/avatarimage"), prisma.websiteSetting .findUnique({ where: { key: "referrals_needed" }, select: { value: true } }) .catch(() => null), prisma.websiteSetting .findUnique({ where: { key: "referral_reward_amount" }, select: { value: true } }) .catch(() => null), prisma.websiteSetting .findFirst({ where: { key: { in: ["referral_reward_currency_type", "referral_reward_currency"] } }, select: { value: true }, }) .catch(() => null), ]); // The session was already validated above; a missing row here is an // exceptional state, so surface the error card rather than redirect from // inside the try (a redirect() throw would be swallowed by the catch). if (!user) throw new Error("user-not-found"); const needed = Number.parseInt(neededRaw?.value ?? "5", 10) || 5; const rewardAmount = Number.parseInt(amountRaw?.value ?? "30", 10) || 0; const rewardCurrency = (currencyRaw?.value ?? "diamonds").trim().toLowerCase(); // --- Referral tally --------------------------------------------------- const referrals = await prisma.userReferrals .findFirst({ where: { userId }, select: { referralsTotal: true }, orderBy: { id: "desc" }, }) .catch(() => null); const referralTotal = referrals ? Number(referrals.referralsTotal) : 0; const canClaim = referralTotal >= needed; const remaining = Math.max(0, needed - referralTotal); // --- Online friends --------------------------------------------------- // Friendships are bidirectional and stored once: the user can be either // user_one_id or user_two_id; the OTHER column is the friend. const friendships = await prisma.messengerFriendships .findMany({ where: { OR: [{ userOneId: userId }, { userTwoId: userId }] }, select: { userOneId: true, userTwoId: true }, }) .catch(() => []); const friendIds = Array.from( new Set( friendships .map((f) => (f.userOneId === userId ? f.userTwoId : f.userOneId)) .filter((id) => id && id !== userId), ), ); const friends = friendIds.length ? await prisma.user .findMany({ where: { id: { in: friendIds } }, select: { id: true, username: true, look: true, motto: true, online: true }, }) .catch(() => []) : []; const onlineFriends = friends.filter((f) => f.online === "1"); const registered = new Date(user.accountCreated * 1000).toISOString().slice(0, 10); const referralLink = `/register?ref=${encodeURIComponent(user.username)}`; content = ( <> {/* Feedback banners --------------------------------------------- */} {claimed ? (
Woah! You have successfully claimed your reward — keep up the good work!
) : null} {error ? (
{getErrorMessage(error)}
) : null} {/* Header card -------------------------------------------------- */} {/* Stat tiles --------------------------------------------------- */}
{/* Online friends ----------------------------------------------- */} View all} padded={onlineFriends.length === 0} > {onlineFriends.length === 0 ? ( No friends online right now. Check back later! ) : (
{onlineFriends.map((friend) => { const avatar = avatarImageUrl(imagerBase ?? "", friend.look, { size: "s", headOnly: true, }); return (
{/* eslint-disable-next-line @next/next/no-img-element */} {`${friend.username}

{friend.username}

{friend.motto || "No motto"}

); })}
)}
{/* Referral card ------------------------------------------------ */}

{rewardAmount > 0 ? `For every ${needed} users who register through your referral link you can claim a reward of ${rewardAmount.toLocaleString()} ${rewardCurrency}!` : `Invite ${needed} users through your referral link to claim a reward!`}

⚠️ Boosting referrals by making your own accounts will lead to removal of all progress, currency, inventory and a potential ban.
{canClaim ? (
) : ( )}
); } catch { content = ( We couldn't load your dashboard right now. Please try again shortly. ); } return
{content}
; }