feat(home): clean shared top bar with live online counter
CI / check (push) Successful in 28s
CI / tests-imaging-browsers (push) Successful in 1m34s
CI / tests-integration (push) Successful in 1m57s
CI / tests-unit (push) Successful in 2m1s
CI / tests-quality (push) Successful in 50s
CI / tests-ui (push) Successful in 4m39s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m29s
CI / check (push) Successful in 28s
CI / tests-imaging-browsers (push) Successful in 1m34s
CI / tests-integration (push) Successful in 1m57s
CI / tests-unit (push) Successful in 2m1s
CI / tests-quality (push) Successful in 50s
CI / tests-ui (push) Successful in 4m39s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m29s
The homepage duplicated the online count and recent-user avatars (hero plus community sidebar), and the public pages each kept their own chrome. Put one AuthTopBar at the top of index, login and register — logo + hotel name plus a live online indicator and the language/clock/theme controls — and drop the duplicated hero rows.
This commit is contained in:
1 parent
9cf1eb615c
commit
b40293bd06
5 files changed
+45
-83
No files matched your search
+2
-74
@@ -10,7 +10,6 @@ import { AnimatedCounter } from "@/components/animated-counter";
|
|||||||
import { AuthTopBar } from "@/components/auth/auth-top-bar";
|
import { AuthTopBar } from "@/components/auth/auth-top-bar";
|
||||||
import { LoginForm } from "@/components/auth/login-form";
|
import { LoginForm } from "@/components/auth/login-form";
|
||||||
import Link from "@/components/link";
|
import Link from "@/components/link";
|
||||||
import { LiveOnlineCounter } from "@/components/live-online-counter";
|
|
||||||
import { Reveal } from "@/components/motion-reveal";
|
import { Reveal } from "@/components/motion-reveal";
|
||||||
import { PublicLoadError } from "@/components/public/load-error";
|
import { PublicLoadError } from "@/components/public/load-error";
|
||||||
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
|
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
|
||||||
@@ -402,9 +401,9 @@ async function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-14 pb-16 pt-2 md:gap-20 md:pb-24">
|
<div className="flex w-full flex-col gap-14 pb-16 pt-2 md:gap-20 md:pb-24">
|
||||||
{/* ── Top bar ── */}
|
{/* ── Top bar: logo + live online + language/clock/theme ── */}
|
||||||
<div className="mx-auto w-full max-w-7xl">
|
<div className="mx-auto w-full max-w-7xl">
|
||||||
<AuthTopBar hotelName={hotelName} logo={logo} />
|
<AuthTopBar hotelName={hotelName} logo={logo} online={online} />
|
||||||
</div>
|
</div>
|
||||||
{/* ── Cinematic hero ── */}
|
{/* ── Cinematic hero ── */}
|
||||||
<Reveal className="-mx-3 sm:-mx-4 md:-mx-6">
|
<Reveal className="-mx-3 sm:-mx-4 md:-mx-6">
|
||||||
@@ -454,46 +453,6 @@ async function Home() {
|
|||||||
style={{ opacity: 0.5, zIndex: 1 }}
|
style={{ opacity: 0.5, zIndex: 1 }}
|
||||||
/>
|
/>
|
||||||
<div className="relative z-10 mx-auto flex min-h-[540px] w-full max-w-6xl flex-col items-center justify-center px-4 py-24 text-center sm:min-h-[580px] lg:min-h-[640px]">
|
<div className="relative z-10 mx-auto flex min-h-[540px] w-full max-w-6xl flex-col items-center justify-center px-4 py-24 text-center sm:min-h-[580px] lg:min-h-[640px]">
|
||||||
<span
|
|
||||||
className="glass-chip gap-2.5 px-4 py-2"
|
|
||||||
style={{
|
|
||||||
fontWeight: 800,
|
|
||||||
letterSpacing: "0.14em",
|
|
||||||
textTransform: "uppercase",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="relative flex h-2 w-2">
|
|
||||||
<span
|
|
||||||
className="absolute inline-flex h-full w-full animate-ping rounded-full opacity-70"
|
|
||||||
style={{ background: "var(--color-primary)" }}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="relative inline-flex h-2 w-2 rounded-full"
|
|
||||||
style={{ background: "var(--color-primary)" }}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="text-[10px] font-black sm:text-xs"
|
|
||||||
style={{ color: "var(--color-text-readable)" }}
|
|
||||||
>
|
|
||||||
{tp("onlineNow")}
|
|
||||||
</span>
|
|
||||||
{online === null ? (
|
|
||||||
<span className="text-xs">{te("unavailable")}</span>
|
|
||||||
) : (
|
|
||||||
<span
|
|
||||||
className="text-sm sm:text-base"
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
"var(--color-primary-readable, var(--color-primary))",
|
|
||||||
fontWeight: 900,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LiveOnlineCounter initial={online} />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<h1
|
<h1
|
||||||
className="mt-7 sm:mt-8"
|
className="mt-7 sm:mt-8"
|
||||||
style={{ fontFamily: "var(--font-nunito)" }}
|
style={{ fontFamily: "var(--font-nunito)" }}
|
||||||
@@ -516,37 +475,6 @@ async function Home() {
|
|||||||
{tp("welcomeBody")}
|
{tp("welcomeBody")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{recentUsers !== null && recentUsers.length > 0 && (
|
|
||||||
<div
|
|
||||||
className="animate-fade-in-up mt-7 inline-flex items-center gap-3 rounded-full border px-3 py-1.5"
|
|
||||||
style={{
|
|
||||||
background: "rgba(255,255,255,0.08)",
|
|
||||||
borderColor: "rgba(255,255,255,0.22)",
|
|
||||||
backdropFilter: "blur(12px)",
|
|
||||||
WebkitBackdropFilter: "blur(12px)",
|
|
||||||
animationDelay: "0.08s",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex -space-x-2.5">
|
|
||||||
{recentUsers.slice(0, 4).map((u) => (
|
|
||||||
<UserAvatarThumbnail
|
|
||||||
key={u.username}
|
|
||||||
figure={u.look}
|
|
||||||
alt={u.username}
|
|
||||||
options={{ direction: 2 }}
|
|
||||||
className="h-9 w-9 rounded-full ring-2 ring-white/70 shadow-lg"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
className="pr-1 text-[10px] font-extrabold uppercase tracking-[0.22em]"
|
|
||||||
style={{ color: "rgba(255,255,255,0.75)" }}
|
|
||||||
>
|
|
||||||
{tp("recentUsers")}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="animate-fade-in-up mt-9 flex flex-wrap items-center justify-center gap-3"
|
className="animate-fade-in-up mt-9 flex flex-wrap items-center justify-center gap-3"
|
||||||
style={{ animationDelay: "0.2s" }}
|
style={{ animationDelay: "0.2s" }}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ export function AuthLandingView({ data, copy, form }: AuthLandingViewProps) {
|
|||||||
<AuthPageFrame
|
<AuthPageFrame
|
||||||
hotelName={data.hotelName}
|
hotelName={data.hotelName}
|
||||||
logo={data.logo}
|
logo={data.logo}
|
||||||
|
online={data.online}
|
||||||
title={copy.title}
|
title={copy.title}
|
||||||
subtitle={copy.subtitle}
|
subtitle={copy.subtitle}
|
||||||
notice={copy.notice}
|
notice={copy.notice}
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ function AvatarGrid({
|
|||||||
interface AuthPageFrameProps {
|
interface AuthPageFrameProps {
|
||||||
hotelName: string;
|
hotelName: string;
|
||||||
logo?: string | null;
|
logo?: string | null;
|
||||||
|
/** Live online count shown in the top bar; omit to hide it. */
|
||||||
|
online?: number;
|
||||||
/** Title of the form card on the right. */
|
/** Title of the form card on the right. */
|
||||||
title: string;
|
title: string;
|
||||||
/** One-line intro above the form. */
|
/** One-line intro above the form. */
|
||||||
@@ -148,6 +150,7 @@ interface AuthPageFrameProps {
|
|||||||
export function AuthPageFrame({
|
export function AuthPageFrame({
|
||||||
hotelName,
|
hotelName,
|
||||||
logo,
|
logo,
|
||||||
|
online,
|
||||||
title,
|
title,
|
||||||
subtitle,
|
subtitle,
|
||||||
notice,
|
notice,
|
||||||
@@ -158,7 +161,7 @@ export function AuthPageFrame({
|
|||||||
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
|
<div className="mx-auto w-full max-w-6xl flex flex-col gap-8 pb-16">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<AuthAurora />
|
<AuthAurora />
|
||||||
<AuthTopBar hotelName={hotelName} logo={logo} />
|
<AuthTopBar hotelName={hotelName} logo={logo} online={online} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Reveal>
|
<Reveal>
|
||||||
|
|||||||
@@ -1,29 +1,33 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Clock } from "@/components/clock";
|
import { Clock } from "@/components/clock";
|
||||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||||
|
import { LiveOnlineCounter } from "@/components/live-online-counter";
|
||||||
import { Reveal } from "@/components/motion-reveal";
|
import { Reveal } from "@/components/motion-reveal";
|
||||||
import { ThemeSwitcher } from "@/components/theme-switcher";
|
import { ThemeSwitcher } from "@/components/theme-switcher";
|
||||||
import { ICON_LIGHTHOUSE } from "@/lib/site-icons";
|
import { ICON_LIGHTHOUSE } from "@/lib/site-icons";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared frosted-glass top bar for the public auth pages (login, register,
|
* Shared frosted-glass top bar for the public pages (index, login, register).
|
||||||
* etc.). Shows the hotel logo + name on the left and language / clock / theme
|
* Shows the hotel logo + name on the left and the live online count, language /
|
||||||
* controls on the right. Defaults mirror the homepage top bar so auth pages
|
* clock / theme controls on the right. One component so every public page keeps
|
||||||
* share one consistent look.
|
* the exact same chrome instead of duplicating it.
|
||||||
*/
|
*/
|
||||||
export function AuthTopBar({
|
export function AuthTopBar({
|
||||||
hotelName,
|
hotelName,
|
||||||
logo,
|
logo,
|
||||||
|
online,
|
||||||
showHotelName = true,
|
showHotelName = true,
|
||||||
}: {
|
}: {
|
||||||
hotelName: string;
|
hotelName: string;
|
||||||
logo?: string | null;
|
logo?: string | null;
|
||||||
|
/** Live online count; omit (or pass null) to hide the indicator. */
|
||||||
|
online?: number | null;
|
||||||
showHotelName?: boolean;
|
showHotelName?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Reveal>
|
<Reveal>
|
||||||
<div
|
<div
|
||||||
className="relative flex items-center justify-between rounded-full border px-3 py-2.5 sm:px-5"
|
className="relative flex items-center justify-between gap-3 rounded-full border px-3 py-2.5 sm:px-5"
|
||||||
style={{
|
style={{
|
||||||
background:
|
background:
|
||||||
"linear-gradient(90deg, color-mix(in srgb, var(--color-surface) 62%, transparent), color-mix(in srgb, var(--color-primary) 7%, transparent))",
|
"linear-gradient(90deg, color-mix(in srgb, var(--color-surface) 62%, transparent), color-mix(in srgb, var(--color-primary) 7%, transparent))",
|
||||||
@@ -35,7 +39,7 @@ export function AuthTopBar({
|
|||||||
"0 1px 3px rgba(0,0,0,0.04), 0 0 0 0.5px color-mix(in srgb, var(--color-text-muted) 6%, transparent)",
|
"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">
|
<div className="flex min-w-0 items-center gap-2 sm:gap-3">
|
||||||
{logo ? (
|
{logo ? (
|
||||||
<Image
|
<Image
|
||||||
src={logo}
|
src={logo}
|
||||||
@@ -69,7 +73,7 @@ export function AuthTopBar({
|
|||||||
)}
|
)}
|
||||||
{showHotelName ? (
|
{showHotelName ? (
|
||||||
<span
|
<span
|
||||||
className="hidden text-sm font-extrabold uppercase tracking-wide sm:inline"
|
className="hidden truncate text-sm font-extrabold uppercase tracking-wide sm:inline"
|
||||||
style={{ color: "var(--color-text-readable)" }}
|
style={{ color: "var(--color-text-readable)" }}
|
||||||
>
|
>
|
||||||
{hotelName}
|
{hotelName}
|
||||||
@@ -83,6 +87,32 @@ export function AuthTopBar({
|
|||||||
"color-mix(in srgb, var(--color-text-muted) 5%, transparent)",
|
"color-mix(in srgb, var(--color-text-muted) 5%, transparent)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{typeof online === "number" && (
|
||||||
|
<span
|
||||||
|
className="flex items-center gap-1.5 px-1"
|
||||||
|
title={hotelName}
|
||||||
|
style={{ color: "var(--color-text-readable)" }}
|
||||||
|
>
|
||||||
|
<span className="relative flex h-2 w-2 shrink-0">
|
||||||
|
<span
|
||||||
|
className="absolute inline-flex h-full w-full animate-ping rounded-full opacity-70"
|
||||||
|
style={{ background: "var(--color-primary)" }}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="relative inline-flex h-2 w-2 rounded-full"
|
||||||
|
style={{ background: "var(--color-primary)" }}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="text-xs font-black tabular-nums"
|
||||||
|
style={{
|
||||||
|
color: "var(--color-primary-readable, var(--color-primary))",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LiveOnlineCounter initial={online} />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
<div
|
<div
|
||||||
className="hidden sm:block w-px h-5"
|
className="hidden sm:block w-px h-5"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const compactCallSites = new Map<string, number>([
|
|||||||
["src/app/(site)/leaderboard/page.tsx", 1],
|
["src/app/(site)/leaderboard/page.tsx", 1],
|
||||||
["src/app/(site)/me/page.tsx", 1],
|
["src/app/(site)/me/page.tsx", 1],
|
||||||
["src/app/(site)/messages/page.tsx", 2],
|
["src/app/(site)/messages/page.tsx", 2],
|
||||||
["src/app/(site)/page.tsx", 2],
|
["src/app/(site)/page.tsx", 1],
|
||||||
["src/app/(site)/radio/leaderboard/page.tsx", 1],
|
["src/app/(site)/radio/leaderboard/page.tsx", 1],
|
||||||
["src/app/(site)/radio/shouts/page.tsx", 1],
|
["src/app/(site)/radio/shouts/page.tsx", 1],
|
||||||
["src/app/(site)/rankings/page.tsx", 1],
|
["src/app/(site)/rankings/page.tsx", 1],
|
||||||
|
|||||||
Reference in new issue
Block a user