import { redirect } from "next/navigation"; import { getFormatter, getTranslations } from "next-intl/server"; import { claimReferral } from "@/actions/referral"; import Link from "@/components/link"; import { CurrencyIcon, type CurrencyKind, } from "@/components/shared/currency-icon"; import { ProfileImage } from "@/components/shared/profile-image"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { SurfaceCard } from "@/components/surface-card"; import { auth } from "@/lib/auth"; import { avatarImageUrl, badgeImageUrl, slugify } from "@/lib/format"; import { withPublicPagePerformance } from "@/lib/public-page-performance"; import { loadDailyRewardState } from "@/lib/services/daily-rewards"; import { loadUserDashboard } from "@/lib/services/user-dashboard"; import CopyReferralButton from "./CopyReferralButton"; import DailyRewardCard from "./DailyRewardCard"; import DashboardTasks from "./DashboardTasks"; import styles from "./dashboard.module.css"; export async function generateMetadata() { const t = await getTranslations("pages.myDashboard"); return { title: t("title") }; } async function MePage({ searchParams, }: { searchParams: Promise<{ claimed?: string; error?: string; daily?: string; }>; }) { 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 [t, format, { claimed, error, daily }] = await Promise.all([ getTranslations("pages.myDashboard"), getFormatter(), searchParams, ]); let data: Awaited>; try { data = await loadUserDashboard(userId); } catch { return (

{t("loadError")}

{t("retry")}
); } // Daily reward state (settings + schedule + the user's last claim). Never // throws: on any DB hiccup it degrades to a disabled state. const dailyState = await loadDailyRewardState(userId); const user = data.userRows[0]; if (!user) return (

{t("loadError")}

{t("login")}
); const { hotelName, neededRaw, rewardAmountRaw, currencySettings, alertRaw, recentRooms, badges, userSettingsRows, friendCountRows, unreadCountRows, referralsRows, friends, currencyRows, dashboardEvent, } = data; const userSettings = userSettingsRows[0]; const friendCount = Number(friendCountRows[0]?.value ?? 0); const unreadCount = Number(unreadCountRows?.[0]?.value ?? 0); const needed = Math.max(1, Number.parseInt(neededRaw ?? "5", 10) || 5); const rewardAmount = Number.parseInt(rewardAmountRaw ?? "30", 10) || 0; const rewardCurrency = ( currencySettings.referral_reward_currency_type ?? currencySettings.referral_reward_currency ?? "diamonds" ) .trim() .toLowerCase(); const rewardKind = ["credits", "duckets", "diamonds", "points"].includes( rewardCurrency, ) ? (rewardCurrency as CurrencyKind) : null; const referralTotal = Math.max( 0, Number(referralsRows?.[0]?.referralsTotal ?? 0), ); const canClaim = referralsRows !== null && referralTotal >= needed && rewardAmount > 0 && rewardKind !== null; const remaining = Math.max(0, needed - referralTotal); const onlineFriends = friends.filter((friend) => friend.online === "1"); const date = (value: number) => value ? format.dateTime(new Date(value * 1000), { dateStyle: "medium" }) : t("never"); const referralLink = `/register?ref=${encodeURIComponent(user.username)}`; const avatarFull = avatarImageUrl(user.look, { direction: 2 }); const wallet = [ { kind: "credits", value: user.credits }, { kind: "duckets", value: currencyRows === null ? null : (currencyRows.find((row) => row.type === 0)?.amount ?? 0), }, { kind: "diamonds", value: currencyRows === null ? null : (currencyRows.find((row) => row.type === 5)?.amount ?? 0), }, ] as const; const errorKey = error && ["not_enough", "no_referrals", "bad_config", "ratelimit"].includes(error) ? error : "error"; return (
{claimed && (

{t("claimed")}

)} {error && (

{t(`errors.${errorKey}`)}

)} {daily === "claimed" && (

{t("dailyClaimed")}

)} {daily && daily !== "claimed" && [ "already_claimed", "disabled", "bad_config", "ratelimit", "error", ].includes(daily) && (

{t(`dailyErrors.${daily}`)}

)}

{t("welcome", { hotel: hotelName })}

{user.username}

{user.motto || t("noMotto")}

{t("registered", { date: date(user.accountCreated) })} ·{" "} {t("lastOnline", { date: date(user.lastOnline) })}

{t("enter", { hotel: hotelName })} →
{alertRaw &&

{alertRaw}

}
{wallet.map((currency) => (
{currency.value === null ? "—" : format.number(currency.value)} {t(`currencies.${currency.kind}`)}
))}
{dashboardEvent.unavailable ? (

{t("eventsUnavailable")}

) : dashboardEvent.event ? (
{t( dashboardEvent.event.phase === "ongoing" ? "eventOngoing" : "eventUpcoming", )}

{dashboardEvent.event.title}

{dashboardEvent.event.typeName} ·{" "}

{t("eventDetails")} →
) : (

{t("noUpcomingEvents")}

)}
{data.friendsUnavailable ? (

{t("loadError")}

) : onlineFriends.length === 0 ? (

{t("noOnlineFriends")}

) : (
{onlineFriends.map((friend) => (
{friend.username} {friend.motto || t("noMotto")}
))}
)}

{t("roomsHint")}

{recentRooms === null ? (

{t("roomsUnavailable")}

) : recentRooms.length === 0 ? (

{t("noRooms")}

) : (
{recentRooms.map((room) => ( {room.name} {room.ownerName} · {room.users}/{room.usersMax} ))}
)}
{[ { label: t("achievements"), value: data.userSettingsUnavailable ? "—" : format.number(userSettings?.achievementScore ?? 0), }, { label: t("respects"), value: data.userSettingsUnavailable ? "—" : format.number(userSettings?.respectsReceived ?? 0), }, { label: t("friends"), value: data.friendsUnavailable ? "—" : format.number(friendCount), }, { label: t("lastLogin"), value: date(user.lastLogin) }, ].map((stat) => (
{stat.label}
{stat.value}
))}
); } export default withPublicPagePerformance("/me", MePage);