Polish content pages: share AuthTopBar, page-grid helper and responsive tables
- Extract shared AuthTopBar frosted top bar; reuse in login & register - Add .page-grid class replacing 48 inline grid styles - Add .table-scroll + .table-cell-truncate helpers; wrap leaderboard/staff tables - Constrain settings page width (max-w-5xl) and stack profile card on mobile - Use theme background var for shop category icon (dark-mode safe)
This commit is contained in:
1 parent
d4677972cd
commit
b57697b2e0
50 files changed
+174
-191
No files matched your search
@@ -96,7 +96,7 @@ export default async function ApplyStaffPage({
|
||||
const appliedRankIds = new Set(myApps.map((a) => a.rankId));
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{submitted === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.submitted")}
|
||||
|
||||
@@ -75,7 +75,7 @@ export default async function ApplyTeamPage({
|
||||
const appliedTeamIds = new Set(myApps.map((a) => a.rankId));
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{submitted === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.submitted")}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default async function BadgesPage() {
|
||||
.catch(() => []);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🏅" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={badges.length === 0}>
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function CommunityPage() {
|
||||
const t = useTranslations("pages.community");
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🌍" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<div className="card-grid sm-2 lg-3">
|
||||
|
||||
@@ -389,7 +389,7 @@ export default function DevelopersPage() {
|
||||
const totalEndpoints = GROUPS.reduce((n, g) => n + g.endpoints.length, 0);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard
|
||||
icon="🧩"
|
||||
title="Developer API"
|
||||
|
||||
@@ -93,7 +93,7 @@ export default async function DrawBadgePage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard
|
||||
icon="🎨"
|
||||
title="Draw a Badge"
|
||||
|
||||
@@ -142,7 +142,7 @@ export default async function EventDetailPage({
|
||||
else if (isFull) disabledReason = t("eventFull");
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<p className="muted" style={{ margin: 0 }}>
|
||||
<Link href="/events">{t("back")}</Link>
|
||||
</p>
|
||||
|
||||
@@ -65,7 +65,7 @@ export default async function EventsPage() {
|
||||
}));
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="📅" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={events.length === 0}>
|
||||
|
||||
@@ -90,7 +90,7 @@ export default async function FriendsPage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{removed === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.removed")}
|
||||
|
||||
@@ -159,7 +159,7 @@ export default async function GuildForumThreadPage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{replied === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.replied")}
|
||||
|
||||
@@ -70,7 +70,7 @@ export default async function NewThreadPage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{errorMessage ? (
|
||||
<div role="alert" style={feedbackStyle("error")}>
|
||||
{errorMessage}
|
||||
|
||||
@@ -120,7 +120,7 @@ export default async function GuildForumPage({
|
||||
const usernameById = new Map(users.map((u) => [u.id, u.username]));
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{posted === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.posted")}
|
||||
|
||||
@@ -125,7 +125,7 @@ export default async function GuildPage({
|
||||
const created = formatDate(new Date(guild.dateCreated * 1000), "date");
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<p style={{ margin: 0 }}>
|
||||
<Link href="/guilds">{t("allGuilds")}</Link>
|
||||
</p>
|
||||
|
||||
@@ -36,7 +36,7 @@ export default async function GuildsPage() {
|
||||
const guilds = await getGuilds();
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🚪" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={guilds.length === 0}>
|
||||
|
||||
@@ -103,7 +103,7 @@ export default async function HelpCategoryPage({
|
||||
const hasButton = Boolean(cat.buttonText && cat.buttonText.trim() !== "");
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<p style={{ margin: 0 }}>
|
||||
<Link href="/help">{t("back")}</Link>
|
||||
</p>
|
||||
|
||||
@@ -112,7 +112,7 @@ export default async function HelpCenterPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard
|
||||
icon="❓"
|
||||
title={t("title")}
|
||||
|
||||
@@ -157,7 +157,7 @@ export default async function HelpTicketDetailPage({
|
||||
];
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{replied === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.replied")}
|
||||
|
||||
@@ -67,7 +67,7 @@ export default async function HelpTicketsPage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{created === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.created")}
|
||||
|
||||
@@ -142,7 +142,7 @@ export default async function LeaderboardPage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard
|
||||
icon="📊"
|
||||
title={t("title")}
|
||||
@@ -165,6 +165,7 @@ export default async function LeaderboardPage({
|
||||
{rows.length === 0 ? (
|
||||
<EmptyState icon="📊">{t("emptyState")}</EmptyState>
|
||||
) : (
|
||||
<div className="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -217,6 +218,7 @@ export default async function LeaderboardPage({
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</ContentCard>
|
||||
</main>
|
||||
|
||||
@@ -2,14 +2,12 @@ import { count, desc, eq } from "drizzle-orm";
|
||||
import { headers } from "next/headers";
|
||||
import Image from "next/image";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { AuthTopBar } from "@/components/auth/auth-top-bar";
|
||||
import { LoginForm } from "@/components/auth/login-form";
|
||||
import { Clock } from "@/components/clock";
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import Link from "@/components/link";
|
||||
import { Reveal } from "@/components/motion-reveal";
|
||||
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
|
||||
import { SurfaceCard } from "@/components/surface-card";
|
||||
import { ThemeSwitcher } from "@/components/theme-switcher";
|
||||
import { cached } from "@/lib/cache";
|
||||
import { db, User } from "@/lib/db";
|
||||
import { resolveHotelName } from "@/lib/hotel-name";
|
||||
@@ -50,74 +48,7 @@ export default async function LoginPage() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
|
||||
{/* ── Top Bar ── */}
|
||||
<Reveal>
|
||||
<div
|
||||
className="relative flex items-center justify-between rounded-xl border px-5 py-3"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-surface) 60%, transparent)",
|
||||
backdropFilter: "blur(14px)",
|
||||
WebkitBackdropFilter: "blur(14px)",
|
||||
borderColor:
|
||||
"color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{logo ? (
|
||||
<Image
|
||||
src={logo}
|
||||
alt={hotelName}
|
||||
width={100}
|
||||
height={28}
|
||||
className="shrink-0 object-contain"
|
||||
unoptimized
|
||||
style={{ maxHeight: 28, width: "auto", height: "auto" }}
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src="/assets/images/icons/lighthouse.png"
|
||||
alt={hotelName}
|
||||
width={28}
|
||||
height={28}
|
||||
className="shrink-0"
|
||||
unoptimized
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className="text-sm font-extrabold uppercase tracking-wide"
|
||||
style={{ color: "var(--color-text-readable)" }}
|
||||
>
|
||||
{hotelName}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-1.5 rounded-lg px-2 py-1"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 4%, transparent)",
|
||||
}}
|
||||
>
|
||||
<LanguageSwitcher />
|
||||
<div
|
||||
className="w-px h-5"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
|
||||
}}
|
||||
/>
|
||||
<Clock />
|
||||
<div
|
||||
className="w-px h-5"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
|
||||
}}
|
||||
/>
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
<AuthTopBar hotelName={hotelName} logo={logo} />
|
||||
|
||||
<Reveal>
|
||||
<div className="flex flex-col gap-8 lg:flex-row">
|
||||
|
||||
@@ -70,7 +70,7 @@ export default async function MarketplacePage() {
|
||||
const total = offers.reduce((sum, o) => sum + o.price, 0);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🛍️" title={t("title")} subtitle={t("subtitle")}>
|
||||
<div className="stat-grid">
|
||||
<StatBlock
|
||||
|
||||
@@ -177,7 +177,7 @@ export default async function MessagesPage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{accepted === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.accepted")}
|
||||
|
||||
@@ -194,7 +194,7 @@ export default async function ArticlePage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{comment === "posted" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.posted")}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function NewsPage() {
|
||||
const articles = await getNewsList(30);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="📰" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={articles.length === 0}>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default async function PhotosPage() {
|
||||
}));
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="📸" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={items.length === 0}>
|
||||
|
||||
@@ -96,7 +96,7 @@ export default async function PollDetailPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<p className="muted" style={{ margin: 0 }}>
|
||||
<Link href="/polls">{t("back")}</Link>
|
||||
</p>
|
||||
|
||||
@@ -48,7 +48,7 @@ export default async function PollsPage() {
|
||||
}));
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="📊" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={polls.length === 0}>
|
||||
|
||||
@@ -65,7 +65,7 @@ export default async function RadioApplyPage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{submitted === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.submitted")}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default async function RadioContestDetailPage({
|
||||
const active = contest.isActive ? "Active" : "Ended";
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<p className="muted" style={{ margin: 0 }}>
|
||||
<Link href="/radio/contests">← Back to contests</Link>
|
||||
</p>
|
||||
|
||||
@@ -23,7 +23,7 @@ export default async function RadioContestsPage() {
|
||||
.catch(() => []);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🎉" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={contests.length === 0}>
|
||||
|
||||
@@ -38,7 +38,7 @@ export default async function RadioGiveawayDetailPage({
|
||||
: null);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<p className="muted" style={{ margin: 0 }}>
|
||||
<Link href="/radio/giveaways">← Back to giveaways</Link>
|
||||
</p>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default async function RadioGiveawaysPage() {
|
||||
.catch(() => []);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🎁" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={giveaways.length === 0}>
|
||||
|
||||
@@ -38,7 +38,7 @@ export default async function RadioLeaderboardPage() {
|
||||
const rows = await loadRows();
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🏆" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={rows.length === 0}>
|
||||
|
||||
@@ -105,7 +105,7 @@ export default async function RadioPage() {
|
||||
const isLive = Boolean(streamUrl);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{/* ── Header: live stream + now playing ─────────────────────── */}
|
||||
<ContentCard
|
||||
icon="📻"
|
||||
|
||||
@@ -74,7 +74,7 @@ export default async function RadioSchedulePage() {
|
||||
const hasAny = schedules.length > 0;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🗓️" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={!hasAny}>
|
||||
|
||||
@@ -73,7 +73,7 @@ export default async function RadioShoutsPage({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{posted === "1" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.posted")}
|
||||
|
||||
@@ -44,7 +44,7 @@ export default async function RankingsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🏆" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<ContentCard padded={users.length === 0}>
|
||||
|
||||
@@ -96,7 +96,7 @@ export default async function RareCategoryPage({
|
||||
badgeBase && cat.badge ? `${badgeBase}/${cat.badge}.gif` : "";
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard
|
||||
icon={badgeSrc ? undefined : "💎"}
|
||||
title={
|
||||
|
||||
@@ -53,7 +53,7 @@ export default async function RareValuesPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="💎" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
{categories.length === 0 ? (
|
||||
|
||||
@@ -29,7 +29,7 @@ export default async function RedeemPage() {
|
||||
if (!user) redirect("/login");
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🎟️" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
<div className="card-grid sm-2">
|
||||
|
||||
@@ -2,14 +2,12 @@ import { count, desc, eq } from "drizzle-orm";
|
||||
import { headers } from "next/headers";
|
||||
import Image from "next/image";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { AuthTopBar } from "@/components/auth/auth-top-bar";
|
||||
import { RegisterForm } from "@/components/auth/register-form";
|
||||
import { Clock } from "@/components/clock";
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import Link from "@/components/link";
|
||||
import { Reveal } from "@/components/motion-reveal";
|
||||
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
|
||||
import { SurfaceCard } from "@/components/surface-card";
|
||||
import { ThemeSwitcher } from "@/components/theme-switcher";
|
||||
import { cached } from "@/lib/cache";
|
||||
import { db, User } from "@/lib/db";
|
||||
import { resolveHotelName } from "@/lib/hotel-name";
|
||||
@@ -51,74 +49,7 @@ export default async function RegisterPage() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
|
||||
{/* ── Top Bar ── */}
|
||||
<Reveal>
|
||||
<div
|
||||
className="relative flex items-center justify-between rounded-xl border px-5 py-3"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-surface) 60%, transparent)",
|
||||
backdropFilter: "blur(14px)",
|
||||
WebkitBackdropFilter: "blur(14px)",
|
||||
borderColor:
|
||||
"color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{logo ? (
|
||||
<Image
|
||||
src={logo}
|
||||
alt={hotelName}
|
||||
width={100}
|
||||
height={28}
|
||||
className="shrink-0 object-contain"
|
||||
unoptimized
|
||||
style={{ maxHeight: 28, width: "auto", height: "auto" }}
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src="/assets/images/icons/lighthouse.png"
|
||||
alt={hotelName}
|
||||
width={28}
|
||||
height={28}
|
||||
className="shrink-0"
|
||||
unoptimized
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className="text-sm font-extrabold uppercase tracking-wide"
|
||||
style={{ color: "var(--color-text-readable)" }}
|
||||
>
|
||||
{hotelName}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-1.5 rounded-lg px-2 py-1"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 4%, transparent)",
|
||||
}}
|
||||
>
|
||||
<LanguageSwitcher />
|
||||
<div
|
||||
className="w-px h-5"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
|
||||
}}
|
||||
/>
|
||||
<Clock />
|
||||
<div
|
||||
className="w-px h-5"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
|
||||
}}
|
||||
/>
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
<AuthTopBar hotelName={hotelName} logo={logo} />
|
||||
|
||||
<Reveal>
|
||||
<div className="flex flex-col gap-8 lg:flex-row lg:items-start">
|
||||
|
||||
@@ -101,7 +101,7 @@ export default async function RoomPage({
|
||||
.filter(Boolean);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<p style={{ margin: 0 }}>
|
||||
<Link href="/" className="btn btn-outline">
|
||||
← Back to the hotel
|
||||
|
||||
@@ -44,7 +44,7 @@ export default async function SettingsPage() {
|
||||
const avatar = avatarImageUrl(user.look);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 pb-14">
|
||||
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6 pb-14">
|
||||
{/* Header */}
|
||||
<Reveal>
|
||||
<SurfaceCard
|
||||
@@ -100,7 +100,7 @@ export default async function SettingsPage() {
|
||||
<SurfaceCard
|
||||
title={t("publicProfile")}
|
||||
icon="/assets/images/icons/navigation/me.png"
|
||||
bodyClassName="p-5 flex items-center gap-5"
|
||||
bodyClassName="p-5 flex flex-col sm:flex-row items-center sm:items-center gap-5"
|
||||
>
|
||||
<div className="shrink-0">
|
||||
<div
|
||||
|
||||
@@ -79,7 +79,7 @@ export default async function SessionsPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{signedOutAll === "1" ? (
|
||||
<p
|
||||
role="status"
|
||||
|
||||
@@ -142,7 +142,7 @@ export default async function ShopPage({
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{boughtMessage ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{boughtMessage}
|
||||
@@ -230,7 +230,7 @@ export default async function ShopPage({
|
||||
borderRadius: "var(--radius-sm)",
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
background: "#f8fafc",
|
||||
background: "var(--color-background)",
|
||||
border: `2px solid ${a.color || "var(--border-color)"}`,
|
||||
flexShrink: 0,
|
||||
overflow: "hidden",
|
||||
|
||||
@@ -41,7 +41,7 @@ export default async function TopUpPage({
|
||||
const rate = creditsPerUnit();
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="💳" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
{sp.status === "cancel" ? (
|
||||
|
||||
@@ -57,7 +57,7 @@ export default async function StaffPage() {
|
||||
]);
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
<ContentCard icon="🛡️" title={t("title")} subtitle={t("subtitle")} />
|
||||
|
||||
{teams.length > 0 ? (
|
||||
@@ -96,6 +96,7 @@ export default async function StaffPage() {
|
||||
{staff.length === 0 ? (
|
||||
<EmptyState icon="🛡️">{t("emptyState")}</EmptyState>
|
||||
) : (
|
||||
<div className="table-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -111,11 +112,14 @@ export default async function StaffPage() {
|
||||
<Link href={`/u/${u.username}`}>{u.username}</Link>
|
||||
</td>
|
||||
<td>{u.rank}</td>
|
||||
<td className="muted">{u.motto || "—"}</td>
|
||||
<td className="muted table-cell-truncate">
|
||||
{u.motto || "—"}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</ContentCard>
|
||||
</main>
|
||||
|
||||
@@ -317,7 +317,7 @@ export default async function ProfilePage({
|
||||
const authorById = new Map(authors.map((a) => [a.id, a]));
|
||||
|
||||
return (
|
||||
<main style={{ display: "grid", gap: "1.5rem" }}>
|
||||
<main className="page-grid">
|
||||
{friend === "sent" ? (
|
||||
<div role="status" style={feedbackStyle("success")}>
|
||||
{t("success.sent")}
|
||||
|
||||
@@ -1400,16 +1400,36 @@ a.content-card {
|
||||
}
|
||||
}
|
||||
|
||||
/* Rich content-page grid (replaces repeated inline `display:grid;gap:1.5rem`). */
|
||||
.page-grid {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* make tables scroll on small screens */
|
||||
@media (max-width: 640px) {
|
||||
table {
|
||||
display: table;
|
||||
}
|
||||
.table-scroll {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border-radius: var(--radius-sm);
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cell text truncation helper for dense tables so long values (motto, URLs)
|
||||
collapse gracefully instead of blowing out row width on small screens. */
|
||||
.table-cell-truncate {
|
||||
max-width: 14rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Safe-area insets for notched phones (header/nav/footer sit at screen edges) */
|
||||
@supports (padding: max(0px)) {
|
||||
.site-header,
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import Image from "next/image";
|
||||
import { Clock } from "@/components/clock";
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import { Reveal } from "@/components/motion-reveal";
|
||||
import { ThemeSwitcher } from "@/components/theme-switcher";
|
||||
|
||||
/**
|
||||
* Shared frosted-glass top bar for the public auth pages (login, register,
|
||||
* etc.). Shows the hotel logo + name on the left and language / clock / theme
|
||||
* controls on the right. Defaults mirror the homepage top bar so auth pages
|
||||
* share one consistent look.
|
||||
*/
|
||||
export function AuthTopBar({
|
||||
hotelName,
|
||||
logo,
|
||||
showHotelName = true,
|
||||
}: {
|
||||
hotelName: string;
|
||||
logo?: string | null;
|
||||
showHotelName?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Reveal>
|
||||
<div
|
||||
className="relative flex items-center justify-between rounded-xl border px-3 py-2.5 sm:px-5 sm:py-3"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-surface) 60%, transparent)",
|
||||
backdropFilter: "blur(14px)",
|
||||
WebkitBackdropFilter: "blur(14px)",
|
||||
borderColor:
|
||||
"color-mix(in srgb, var(--color-text-muted) 8%, transparent)",
|
||||
boxShadow:
|
||||
"0 1px 3px rgba(0,0,0,0.04), 0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 6%, transparent)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
{logo ? (
|
||||
<Image
|
||||
src={logo}
|
||||
alt={hotelName}
|
||||
width={100}
|
||||
height={28}
|
||||
className="shrink-0 object-contain"
|
||||
unoptimized
|
||||
style={{ maxHeight: 28, width: "auto", height: "auto" }}
|
||||
/>
|
||||
) : (
|
||||
<Image
|
||||
src="/assets/images/icons/lighthouse.png"
|
||||
alt={hotelName}
|
||||
width={28}
|
||||
height={28}
|
||||
className="shrink-0"
|
||||
unoptimized
|
||||
/>
|
||||
)}
|
||||
{showHotelName ? (
|
||||
<span
|
||||
className="hidden sm:inline text-sm font-extrabold uppercase tracking-wide"
|
||||
style={{ color: "var(--color-text-readable)" }}
|
||||
>
|
||||
{hotelName}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-1 sm:gap-1.5 rounded-lg px-1.5 py-1 sm:px-2"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 4%, transparent)",
|
||||
}}
|
||||
>
|
||||
<LanguageSwitcher />
|
||||
<div
|
||||
className="hidden sm:block w-px h-5"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
|
||||
}}
|
||||
/>
|
||||
<Clock />
|
||||
<div
|
||||
className="w-px h-5"
|
||||
style={{
|
||||
background:
|
||||
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
|
||||
}}
|
||||
/>
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</Reveal>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user