{t("loadError")}
+ + {t("login")} + +diff --git a/src/app/(site)/me/CopyReferralButton.tsx b/src/app/(site)/me/CopyReferralButton.tsx index 87283a57b5..4bd73512a9 100644 --- a/src/app/(site)/me/CopyReferralButton.tsx +++ b/src/app/(site)/me/CopyReferralButton.tsx @@ -1,11 +1,16 @@ "use client"; +import { useTranslations } from "next-intl"; import { useState } from "react"; export default function CopyReferralButton({ value }: { value: string }) { const [copied, setCopied] = useState(false); + const [failed, setFailed] = useState(false); + const t = useTranslations("pages.myDashboard"); async function copy() { + setFailed(false); + setCopied(false); const absolute = typeof window !== "undefined" && value.startsWith("/") ? `${window.location.origin}${value}` @@ -20,9 +25,11 @@ export default function CopyReferralButton({ value }: { value: string }) { document.body.appendChild(ta); ta.select(); try { - document.execCommand("copy"); + if (!document.execCommand("copy")) throw new Error("copy-failed"); } catch { - /* give up silently */ + setFailed(true); + document.body.removeChild(ta); + return; } document.body.removeChild(ta); } @@ -31,17 +38,21 @@ export default function CopyReferralButton({ value }: { value: string }) { } return ( - + <> + + {failed &&
{t("copyFailed")}
} + > ); } diff --git a/src/app/(site)/me/dashboard.module.css b/src/app/(site)/me/dashboard.module.css new file mode 100644 index 0000000000..f0fc6bd891 --- /dev/null +++ b/src/app/(site)/me/dashboard.module.css @@ -0,0 +1,249 @@ +.dashboard { + display: grid; + gap: 20px; + padding-bottom: 24px; + min-width: 0; +} +.hero { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 24px; + padding: 24px; + background: linear-gradient( + 130deg, + color-mix(in srgb, var(--color-primary) 10%, var(--color-surface)), + var(--color-surface) + ); +} +.avatar { + flex-shrink: 0; + object-fit: contain; + image-rendering: pixelated; + border-radius: 16px; + background: color-mix(in srgb, var(--color-primary) 10%, transparent); +} +.identity { + flex: 1; + min-width: 180px; + overflow-wrap: anywhere; +} +.identity h1 { + font-size: clamp(1.6rem, 3vw, 2rem); + margin: 4px 0; +} +.dates { + font-size: 0.8rem; + color: var(--color-text-muted); + margin-top: 12px; +} +.shortcuts { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} +.shortcut { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 14px; + border-radius: 12px; + background: var(--color-surface); + border: 1px solid var(--color-border); + font-weight: 600; + color: var(--color-text-readable); + text-decoration: none; +} +.shortcut:hover, +.friend:hover, +.room:hover { + background: color-mix(in srgb, var(--color-primary) 8%, var(--color-surface)); +} +.count { + border-radius: 999px; + padding: 2px 7px; + background: var(--color-danger); + color: white; + font-size: 0.75rem; +} +.wallet { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} +.currency { + display: flex; + align-items: center; + gap: 14px; + padding: 20px; +} +.currency > div { + display: grid; + min-width: 0; +} +.currency strong { + font-size: 1.3rem; + font-variant-numeric: tabular-nums; + overflow-wrap: anywhere; +} +.currency span { + font-size: 0.8rem; + color: var(--color-text-muted); +} +.columns { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(280px, 350px); + gap: 20px; + align-items: start; +} +.stack { + display: grid; + gap: 20px; + min-width: 0; +} +.friends, +.rooms { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); + gap: 12px; +} +.friend, +.room { + display: flex; + gap: 12px; + padding: 12px; + border: 1px solid var(--color-border); + border-radius: 12px; + color: var(--color-text-readable); + text-decoration: none; + min-width: 0; +} +.friend > div, +.room { + display: grid; + min-width: 0; +} +.friend strong, +.friend span, +.room strong { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.friend span, +.room span { + font-size: 0.8rem; +} +.stats { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 20px; + margin: 0; +} +.stats dt { + font-size: 0.8rem; +} +.stats dd { + margin: 4px 0 0; + font-weight: 700; +} +.feedback { + margin: 0; + border: 1px solid var(--color-border); + border-left: 4px solid var(--color-primary); + border-radius: 12px; + background: var(--color-surface); + padding: 16px; +} +.progressLabel { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-top: 16px; + font-size: 0.85rem; +} +.progress { + width: 100%; + height: 10px; + accent-color: var(--color-primary); +} +.reward { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85rem; +} +.referralInput { + width: 100%; + min-width: 0; + margin: 8px 0; +} +.claim { + margin-top: 16px; +} +.claim button { + width: 100%; +} +.rules { + border-top: 1px solid var(--color-border); + margin-top: 16px; + padding-top: 12px; + font-size: 0.8rem; +} +.rules summary { + cursor: pointer; +} +.badges { + display: flex; + flex-wrap: wrap; + gap: 10px; +} +.badges img { + object-fit: contain; + image-rendering: pixelated; +} +@media (max-width: 760px) { + .columns { + grid-template-columns: minmax(0, 1fr); + } + .shortcuts { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .hero { + padding: 18px; + gap: 16px; + } + .hero > a { + width: 100%; + text-align: center; + } + .currency { + padding: 12px; + gap: 8px; + flex-wrap: wrap; + } + .currency strong { + font-size: 1rem; + } +} +.progress { + appearance: none; + border: 0; + border-radius: 999px; + overflow: hidden; + background: var(--color-border); +} +.progress::-webkit-progress-bar { + background: var(--color-border); + border-radius: 999px; +} +.progress::-webkit-progress-value { + background: var(--color-primary); + border-radius: 999px; +} +.progress::-moz-progress-bar { + background: var(--color-primary); + border-radius: 999px; +} diff --git a/src/app/(site)/me/page.tsx b/src/app/(site)/me/page.tsx index 6fe78861d2..b4e3f445ea 100644 --- a/src/app/(site)/me/page.tsx +++ b/src/app/(site)/me/page.tsx @@ -1,367 +1,233 @@ -import Image from "next/image"; import { redirect } from "next/navigation"; -import type { CSSProperties, ReactNode } from "react"; +import { getFormatter, getTranslations } from "next-intl/server"; import { claimReferral } from "@/actions/referral"; import Link from "@/components/link"; - -import { Reveal } from "@/components/motion-reveal"; +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 } from "@/lib/format"; import { loadUserDashboard } from "@/lib/services/user-dashboard"; import CopyReferralButton from "./CopyReferralButton"; +import styles from "./dashboard.module.css"; -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{t("loadError")}
+ + {t("retry")} + +{t("loadError")}
+ + {t("login")} + ++ {t("claimed")} +
+ )} + {error && ( ++ {t(`errors.${errorKey}`)} +
+ )} +{t("welcome", { hotel: hotelName })}
+{user.motto || t("noMotto")}
++ {t("registered", { date: date(user.accountCreated) })} ·{" "} + {t("lastOnline", { date: date(user.lastOnline) })} +
+- {user.motto || "No motto"} -
-{alertRaw}
} +- {alertRaw} -
-- {onlineFriends.length === 0 - ? "None of your friends are online right now" - : `${onlineFriends.length} of your ${friends.length} friends online`} -
-- No friends online right now. Check back later! -
-{t("noOnlineFriends")}
) : ( -- {friend.username} -
-- {friend.motto || "No motto"} -
- - {" "} - Online +- 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!`} -
-- Newest rooms in the hotel -
-{t("roomsHint")}
+ {recentRooms.length === 0 ? ( +{t("noRooms")}
+ ) : ( +- {room.name} -
-- {room.ownerName} · {room.users}/{room.usersMax} -
+ {room.name} + + {room.ownerName} · {room.users}/{room.usersMax} + ))}- {badges.length} badge{badges.length !== 1 ? "s" : ""} equipped -
-- We couldn't load your dashboard right now. Please try again - shortly. -
-