import { and, asc, count, desc, eq, gt, or } from "drizzle-orm"; import Image from "next/image"; import Link from "next/link"; import { redirect } from "next/navigation"; import type { CSSProperties, ReactNode } from "react"; import { claimReferral } from "@/actions/referral"; import { Reveal } from "@/components/motion-reveal"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { SurfaceCard } from "@/components/surface-card"; import { auth } from "@/lib/auth"; import { db, MessengerFriendships, MessengerOffline, Rooms, User, UserReferrals, UsersBadges, UsersSettings, } from "@/lib/db"; import { avatarImageUrl } from "@/lib/format"; import { resolveHotelName } from "@/lib/hotel-name"; import { siteSettings } from "@/lib/services/site-settings"; import CopyReferralButton from "./CopyReferralButton"; export const metadata = { title: "My dashboard" }; type SearchParams = Promise<{ claimed?: string; error?: string }>; 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 { return ERROR_MESSAGES[error] ?? 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: ReactNode; try { const BADGE_IMG_BASE = "https://images.habbo.com/c_images/album1584"; const [ userRows, hotelName, neededRaw, rewardAmountRaw, currencySettings, alertRaw, recentRooms, badges, userSettingsRows, friendCountRows, unreadCountRows, referralsRows, friends, ] = await Promise.all([ db .select({ id: User.id, username: User.username, look: User.look, motto: User.motto, rank: User.rank, credits: User.credits, accountCreated: User.accountCreated, lastLogin: User.lastLogin, lastOnline: User.lastOnline, }) .from(User) .where(eq(User.id, userId)) .limit(1), resolveHotelName(), siteSettings.get("referrals_needed", "5"), siteSettings.get("referral_reward_amount", "30"), siteSettings.getMany([ "referral_reward_currency_type", "referral_reward_currency", ]), siteSettings.get("hotel_alert_banner", ""), db .select({ id: Rooms.id, name: Rooms.name, ownerName: Rooms.ownerName, users: Rooms.users, usersMax: Rooms.usersMax, }) .from(Rooms) .orderBy(desc(Rooms.id)) .limit(6) .catch(() => []), db .select({ badgeCode: UsersBadges.badgeCode, slotId: UsersBadges.slotId, }) .from(UsersBadges) .where(and(eq(UsersBadges.userId, userId), gt(UsersBadges.slotId, 0))) .orderBy(asc(UsersBadges.slotId)) .catch(() => []), db .select({ achievementScore: UsersSettings.achievementScore, respectsReceived: UsersSettings.respectsReceived, }) .from(UsersSettings) .where(eq(UsersSettings.userId, userId)) .limit(1) .catch(() => []), db .select({ value: count() }) .from(MessengerFriendships) .where( or( eq(MessengerFriendships.userOneId, userId), eq(MessengerFriendships.userTwoId, userId), ), ) .catch(() => [{ value: 0 }]), db .select({ value: count() }) .from(MessengerOffline) .where(eq(MessengerOffline.userId, userId)) .catch(() => [{ value: 0 }]), db .select({ referralsTotal: UserReferrals.referralsTotal }) .from(UserReferrals) .where(eq(UserReferrals.userId, userId)) .orderBy(desc(UserReferrals.id)) .limit(1) .catch(() => []), db .select({ id: User.id, username: User.username, look: User.look, motto: User.motto, online: User.online, }) .from(MessengerFriendships) .innerJoin( User, or( and( eq(MessengerFriendships.userOneId, userId), eq(User.id, MessengerFriendships.userTwoId), ), and( eq(MessengerFriendships.userTwoId, userId), eq(User.id, MessengerFriendships.userOneId), ), ), ) .where( or( eq(MessengerFriendships.userOneId, userId), eq(MessengerFriendships.userTwoId, userId), ), ) .catch(() => []), ]); const user = userRows[0] ?? null; if (!user) throw new Error("user-not-found"); const userSettings = userSettingsRows[0] ?? null; const friendCount = Number(friendCountRows[0]?.value ?? 0); const unreadCount = Number(unreadCountRows[0]?.value ?? 0); const needed = 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 referralTotal = referralsRows[0] ? Number(referralsRows[0].referralsTotal) : 0; const canClaim = referralTotal >= needed; const remaining = Math.max(0, needed - referralTotal); const onlineFriends = friends.filter((f) => f.online === "1"); const registered = new Date(user.accountCreated * 1000) .toISOString() .slice(0, 10); const lastLoginDate = user.lastLogin ? new Date(user.lastLogin * 1000).toISOString().slice(0, 10) : "Never"; const lastOnlineDate = user.lastOnline ? new Date(user.lastOnline * 1000).toISOString().slice(0, 10) : "Never"; const referralLink = `/register?ref=${encodeURIComponent(user.username)}`; const avatarFull = avatarImageUrl(user.look, { direction: 2, }); content = (
{claimed ? (
Woah! You have successfully claimed your reward — keep up the good work!
) : null} {error ? (
{getErrorMessage(error)}
) : null}
Enter {hotelName}
Rank {user.rank}

{user.username}

{user.motto || "No motto"}

Registered:{" "} {registered} Last online:{" "} {lastOnlineDate}
{alertRaw ? (

{alertRaw}

) : null}
{[ { value: user.credits.toLocaleString(), label: "Credits" }, { value: `Rank ${user.rank}`, label: "Hotel rank" }, { value: (userSettings?.achievementScore ?? 0).toLocaleString(), label: "Achievement score", }, { value: (userSettings?.respectsReceived ?? 0).toLocaleString(), label: "Respects", }, { value: friendCount.toLocaleString(), label: "Friends" }, { value: lastLoginDate, label: "Last login" }, ].map((s) => (
{s.value}
{s.label}
))}

Online friends

{onlineFriends.length === 0 ? "None of your friends are online right now" : `${onlineFriends.length} of your ${friends.length} friends online`}

View all
{onlineFriends.length === 0 ? (

No friends online right now. Check back later!

) : (
{onlineFriends.map((friend) => (

{friend.username}

{friend.motto || "No motto"}

{" "} Online
))}
)}

Refer a friend ({referralTotal}/{needed})

Refer new users and earn in-game rewards

{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 ? (
) : ( )}
{recentRooms.length > 0 && (

Recent rooms

Newest rooms in the hotel

View all
{recentRooms.map((room) => (

{room.name}

{room.ownerName} · {room.users}/{room.usersMax}

))}
)} {badges.length > 0 && (

Badges

{badges.length} badge{badges.length !== 1 ? "s" : ""} equipped

{badges.map((badge) => ( {badge.badgeCode} ))}
)}

Quick links

{[ { href: "/messages", label: "Messages", badge: unreadCount }, { href: `/u/${user.username}`, label: "My profile", badge: 0 }, { href: "/settings", label: "Settings", badge: 0 }, { href: "/shop", label: "Shop", badge: 0 }, ].map((link) => ( {link.label} {link.badge > 0 && ( {link.badge > 99 ? "99+" : link.badge} )} ))}
); } catch { content = (

We couldn't load your dashboard right now. Please try again shortly.

); } return
{content}
; }