From 7a41775c7e1ae35284c052be90771784939d00b5 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 29 Aug 2026 19:27:13 +0200 Subject: [PATCH 1/4] fix: disable route prefetching app-wide to avoid spurious requests Wrap next/link in a shared Link component that ships prefetch=false by default, so no route is ever prefetched (viewport or hover) anymore, and drop the DNS prefetch hint. Removes hidden background requests that were the source of intermittent issues. --- src/app/(site)/community/page.tsx | 2 +- src/app/(site)/error.tsx | 2 +- src/app/(site)/events/[slugOrId]/page.tsx | 2 +- src/app/(site)/events/page.tsx | 2 +- src/app/(site)/forgot/page.tsx | 2 +- src/app/(site)/friends/page.tsx | 2 +- src/app/(site)/guilds/[id]/forum/[threadId]/page.tsx | 2 +- src/app/(site)/guilds/[id]/forum/new/page.tsx | 2 +- src/app/(site)/guilds/[id]/forum/page.tsx | 2 +- src/app/(site)/guilds/[id]/page.tsx | 2 +- src/app/(site)/guilds/page.tsx | 2 +- src/app/(site)/help/[category]/page.tsx | 2 +- src/app/(site)/help/page.tsx | 2 +- src/app/(site)/help/tickets/[id]/page.tsx | 2 +- src/app/(site)/help/tickets/page.tsx | 2 +- src/app/(site)/leaderboard/page.tsx | 2 +- src/app/(site)/login/page.tsx | 2 +- src/app/(site)/me/page.tsx | 3 +-- src/app/(site)/messages/page.tsx | 2 +- src/app/(site)/news/[...slug]/page.tsx | 2 +- src/app/(site)/news/page.tsx | 2 +- src/app/(site)/page.tsx | 2 +- src/app/(site)/polls/[id]/page.tsx | 2 +- src/app/(site)/polls/page.tsx | 2 +- src/app/(site)/radio/contests/[id]/page.tsx | 2 +- src/app/(site)/radio/contests/page.tsx | 2 +- src/app/(site)/radio/giveaways/[id]/page.tsx | 2 +- src/app/(site)/radio/giveaways/page.tsx | 2 +- src/app/(site)/radio/layout.tsx | 2 +- src/app/(site)/radio/leaderboard/page.tsx | 2 +- src/app/(site)/radio/page.tsx | 2 +- src/app/(site)/rankings/page.tsx | 2 +- src/app/(site)/rares/[category]/page.tsx | 2 +- src/app/(site)/rares/page.tsx | 2 +- src/app/(site)/register/page.tsx | 2 +- src/app/(site)/reset/page.tsx | 2 +- src/app/(site)/room/[id]/page.tsx | 2 +- src/app/(site)/search/page.tsx | 2 +- src/app/(site)/settings/2fa/page.tsx | 2 +- src/app/(site)/settings/page.tsx | 2 +- src/app/(site)/shop/page.tsx | 2 +- src/app/(site)/staff/page.tsx | 2 +- src/app/(site)/u/[username]/page.tsx | 2 +- src/app/(site)/verify/page.tsx | 2 +- src/app/admin/ads/[id]/page.tsx | 2 +- src/app/admin/ads/ads-table.tsx | 2 +- src/app/admin/ads/new/page.tsx | 2 +- src/app/admin/ads/page.tsx | 2 +- src/app/admin/applications/page.tsx | 2 +- src/app/admin/articles/[id]/page.tsx | 2 +- src/app/admin/articles/new/page.tsx | 2 +- src/app/admin/articles/page.tsx | 2 +- src/app/admin/calendar/[id]/page.tsx | 2 +- src/app/admin/calendar/page.tsx | 2 +- src/app/admin/catalog/[id]/catalog-page-form.tsx | 2 +- src/app/admin/catalog/[id]/page.tsx | 2 +- src/app/admin/catalog/builder-club/bc-manager.tsx | 2 +- src/app/admin/catalog/catalog-table.tsx | 2 +- src/app/admin/devops/page.tsx | 2 +- src/app/admin/error.tsx | 2 +- src/app/admin/events/events-table.tsx | 2 +- src/app/admin/events/page.tsx | 2 +- src/app/admin/guilds/[id]/page.tsx | 2 +- src/app/admin/guilds/page.tsx | 2 +- src/app/admin/help-questions/[id]/page.tsx | 2 +- src/app/admin/help-questions/new/page.tsx | 2 +- src/app/admin/help-questions/page.tsx | 2 +- .../help-tickets/[id]/admin-help-ticket-detail.tsx | 2 +- src/app/admin/help-tickets/help-tickets-table.tsx | 2 +- src/app/admin/housekeeping/page.tsx | 2 +- src/app/admin/items/[id]/items-base-edit-form.tsx | 2 +- src/app/admin/items/items-table.tsx | 2 +- src/app/admin/layout.tsx | 2 +- src/app/admin/marketplace/page.tsx | 2 +- src/app/admin/moderation/cfh/[id]/cfh-detail.tsx | 2 +- src/app/admin/moderation/cfh/cfh-table.tsx | 2 +- src/app/admin/moderation/page.tsx | 2 +- src/app/admin/moderation/team/page.tsx | 2 +- src/app/admin/page.tsx | 2 +- src/app/admin/permissions/[id]/rank-edit-client.tsx | 2 +- src/app/admin/permissions/permissions-list.tsx | 2 +- src/app/admin/photos/page.tsx | 2 +- src/app/admin/polls/page.tsx | 2 +- src/app/admin/polls/polls-table.tsx | 2 +- src/app/admin/radio/embed/page.tsx | 2 +- src/app/admin/rooms/[id]/furni/furni-client.tsx | 2 +- src/app/admin/rooms/edit/[id]/page.tsx | 2 +- src/app/admin/rooms/rooms-table.tsx | 2 +- src/app/admin/rooms/show/[id]/page.tsx | 2 +- src/app/admin/shop/[id]/page.tsx | 2 +- src/app/admin/shop/new/page.tsx | 2 +- src/app/admin/shop/page.tsx | 2 +- src/app/admin/studio/layout.tsx | 2 +- src/app/admin/tickets/[id]/admin-ticket-detail.tsx | 2 +- src/app/admin/tickets/tickets-table.tsx | 2 +- src/app/admin/transactions/transactions-table.tsx | 2 +- src/app/admin/translations/emulator/page.tsx | 2 +- src/app/admin/translations/translations-tabs.tsx | 2 +- src/app/admin/users/[id]/user-activity-log.tsx | 2 +- src/app/admin/users/[id]/user-rooms.tsx | 2 +- src/app/admin/users/_components/user-account-info.tsx | 2 +- src/app/admin/users/_components/user-hero.tsx | 2 +- .../admin/users/_components/user-sanction-timeline.tsx | 2 +- src/app/admin/users/multi-accounts/page.tsx | 2 +- src/app/admin/users/users-table.tsx | 2 +- src/app/admin/vpn/page.tsx | 2 +- src/app/client/client-view.tsx | 10 ++-------- src/app/error.tsx | 2 +- src/app/layout.tsx | 1 - src/app/mod/layout.tsx | 2 +- src/app/mod/page.tsx | 2 +- src/app/mod/team/page.tsx | 2 +- src/app/mod/tickets/page.tsx | 2 +- src/app/mod/users/[id]/page.tsx | 2 +- src/app/mod/users/page.tsx | 2 +- src/app/not-found.tsx | 2 +- src/components/admin/admin-breadcrumb.tsx | 2 +- src/components/admin/admin-nav-link.tsx | 2 +- src/components/admin/admin-section-tabs.tsx | 2 +- src/components/admin/admin-simple-pager.tsx | 2 +- src/components/admin/article-card.tsx | 2 +- src/components/admin/catalog-manager/inline-editor.tsx | 2 +- src/components/admin/catalog-tree.tsx | 2 +- src/components/admin/stats-card.tsx | 2 +- src/components/admin/ticket-queue-banner.tsx | 2 +- src/components/admin/unified-tickets-table.tsx | 2 +- src/components/auth/login-form.tsx | 2 +- src/components/auth/register-form.tsx | 2 +- src/components/link.tsx | 6 ++++++ src/components/navigation.tsx | 2 +- src/components/site-header.tsx | 2 +- src/components/surface-card.tsx | 2 +- src/components/top-header.tsx | 2 +- .../housekeeping/foundation/shell/context-nav.tsx | 2 +- .../housekeeping/foundation/shell/domain-rail.tsx | 2 +- .../foundation/shell/housekeeping-shell.tsx | 2 +- 136 files changed, 141 insertions(+), 143 deletions(-) create mode 100644 src/components/link.tsx diff --git a/src/app/(site)/community/page.tsx b/src/app/(site)/community/page.tsx index 0a276be573..49a697e64f 100644 --- a/src/app/(site)/community/page.tsx +++ b/src/app/(site)/community/page.tsx @@ -1,5 +1,5 @@ -import Link from "next/link"; import { useTranslations } from "next-intl"; +import Link from "@/components/link"; import { ContentCard } from "@/components/public/ui"; export const metadata = { title: "Community" }; diff --git a/src/app/(site)/error.tsx b/src/app/(site)/error.tsx index 0f6d9bb9ab..696637f592 100644 --- a/src/app/(site)/error.tsx +++ b/src/app/(site)/error.tsx @@ -1,10 +1,10 @@ "use client"; import { Home, RefreshCw } from "lucide-react"; -import Link from "next/link"; import { useTranslations } from "next-intl"; import { useEffect } from "react"; import { ErrorScreen } from "@/components/error-screen"; +import Link from "@/components/link"; /** * Site route-segment error boundary. Renders inside the site shell layout. diff --git a/src/app/(site)/events/[slugOrId]/page.tsx b/src/app/(site)/events/[slugOrId]/page.tsx index 6d6ef9d4dd..d6137dc60d 100644 --- a/src/app/(site)/events/[slugOrId]/page.tsx +++ b/src/app/(site)/events/[slugOrId]/page.tsx @@ -1,8 +1,8 @@ import { and, asc, count, eq, inArray } from "drizzle-orm"; import Image from "next/image"; -import Link from "next/link"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { ContentCard, EmptyState } from "@/components/public/ui"; import { auth } from "@/lib/auth"; import { diff --git a/src/app/(site)/events/page.tsx b/src/app/(site)/events/page.tsx index ed625bafa1..89f66a368e 100644 --- a/src/app/(site)/events/page.tsx +++ b/src/app/(site)/events/page.tsx @@ -1,7 +1,7 @@ import { count, desc, eq, inArray } from "drizzle-orm"; import Image from "next/image"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { ContentCard, EmptyState } from "@/components/public/ui"; import { db, diff --git a/src/app/(site)/forgot/page.tsx b/src/app/(site)/forgot/page.tsx index 289fea1b7c..c32dfe640d 100644 --- a/src/app/(site)/forgot/page.tsx +++ b/src/app/(site)/forgot/page.tsx @@ -1,8 +1,8 @@ import { headers } from "next/headers"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { requestReset } from "@/actions/password-reset"; import { CaptchaWidget } from "@/components/auth/captcha-widget"; +import Link from "@/components/link"; import { ContentCard } from "@/components/public/ui"; import { captchaConfig } from "@/lib/services/captcha"; diff --git a/src/app/(site)/friends/page.tsx b/src/app/(site)/friends/page.tsx index 85a5601856..8376ca445b 100644 --- a/src/app/(site)/friends/page.tsx +++ b/src/app/(site)/friends/page.tsx @@ -1,9 +1,9 @@ import { asc, eq, inArray, or } from "drizzle-orm"; -import Link from "next/link"; import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { CSSProperties } from "react"; import { removeFriendship } from "@/actions/messenger"; +import Link from "@/components/link"; import { ContentCard, EmptyState, OnlineBadge } from "@/components/public/ui"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { auth } from "@/lib/auth"; diff --git a/src/app/(site)/guilds/[id]/forum/[threadId]/page.tsx b/src/app/(site)/guilds/[id]/forum/[threadId]/page.tsx index 238664d50a..f20b456b1a 100644 --- a/src/app/(site)/guilds/[id]/forum/[threadId]/page.tsx +++ b/src/app/(site)/guilds/[id]/forum/[threadId]/page.tsx @@ -1,9 +1,9 @@ import { and, asc, eq, inArray } from "drizzle-orm"; -import Link from "next/link"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { CSSProperties } from "react"; import { replyToThread } from "@/actions/social"; +import Link from "@/components/link"; import { ContentCard, EmptyState } from "@/components/public/ui"; import { auth } from "@/lib/auth"; import { diff --git a/src/app/(site)/guilds/[id]/forum/new/page.tsx b/src/app/(site)/guilds/[id]/forum/new/page.tsx index c5b7294b6c..7fc66365de 100644 --- a/src/app/(site)/guilds/[id]/forum/new/page.tsx +++ b/src/app/(site)/guilds/[id]/forum/new/page.tsx @@ -1,9 +1,9 @@ import { eq } from "drizzle-orm"; -import Link from "next/link"; import { notFound, redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { CSSProperties } from "react"; import { postThread } from "@/actions/social"; +import Link from "@/components/link"; import { ContentCard } from "@/components/public/ui"; import { auth } from "@/lib/auth"; import { db, Guilds } from "@/lib/db"; diff --git a/src/app/(site)/guilds/[id]/forum/page.tsx b/src/app/(site)/guilds/[id]/forum/page.tsx index e8d2e1fe65..ae4df681cd 100644 --- a/src/app/(site)/guilds/[id]/forum/page.tsx +++ b/src/app/(site)/guilds/[id]/forum/page.tsx @@ -1,8 +1,8 @@ import { and, desc, eq, inArray } from "drizzle-orm"; -import Link from "next/link"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { CSSProperties } from "react"; +import Link from "@/components/link"; import { ContentCard, EmptyState } from "@/components/public/ui"; import { db, Guilds, GuildsForumsThreads, User } from "@/lib/db"; import { formatDate } from "@/lib/format-date"; diff --git a/src/app/(site)/guilds/[id]/page.tsx b/src/app/(site)/guilds/[id]/page.tsx index df6056e961..ff59586311 100644 --- a/src/app/(site)/guilds/[id]/page.tsx +++ b/src/app/(site)/guilds/[id]/page.tsx @@ -1,7 +1,7 @@ import { asc, eq, inArray } from "drizzle-orm"; -import Link from "next/link"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { ContentCard, EmptyState, StatBlock } from "@/components/public/ui"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { db, Guilds, GuildsMembers, User } from "@/lib/db"; diff --git a/src/app/(site)/guilds/page.tsx b/src/app/(site)/guilds/page.tsx index 0da0d8685f..c1f80ddc2f 100644 --- a/src/app/(site)/guilds/page.tsx +++ b/src/app/(site)/guilds/page.tsx @@ -1,6 +1,6 @@ import { desc } from "drizzle-orm"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { ContentCard, EmptyState } from "@/components/public/ui"; import { db, Guilds } from "@/lib/db"; import { excerpt } from "@/lib/format"; diff --git a/src/app/(site)/help/[category]/page.tsx b/src/app/(site)/help/[category]/page.tsx index 71f35f2d30..65cb9e52e0 100644 --- a/src/app/(site)/help/[category]/page.tsx +++ b/src/app/(site)/help/[category]/page.tsx @@ -1,7 +1,7 @@ import { asc, eq, ne } from "drizzle-orm"; -import Link from "next/link"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { ContentCard } from "@/components/public/ui"; import { SanitizedHtml } from "@/components/shared/sanitized-html"; import { db, WebsiteHelpCenterCategories } from "@/lib/db"; diff --git a/src/app/(site)/help/page.tsx b/src/app/(site)/help/page.tsx index 3497d37e73..7b758b5e6e 100644 --- a/src/app/(site)/help/page.tsx +++ b/src/app/(site)/help/page.tsx @@ -1,6 +1,6 @@ import { asc } from "drizzle-orm"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { ContentCard, EmptyState } from "@/components/public/ui"; import { db, diff --git a/src/app/(site)/help/tickets/[id]/page.tsx b/src/app/(site)/help/tickets/[id]/page.tsx index 1e2048e03d..a7293404f5 100644 --- a/src/app/(site)/help/tickets/[id]/page.tsx +++ b/src/app/(site)/help/tickets/[id]/page.tsx @@ -1,9 +1,9 @@ import { asc, eq, inArray } from "drizzle-orm"; -import Link from "next/link"; import { notFound, redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { CSSProperties } from "react"; import { closeHelpTicket, replyHelpTicket } from "@/actions/help-tickets"; +import Link from "@/components/link"; import { ContentCard } from "@/components/public/ui"; import { positiveBigInt } from "@/lib/api"; import { auth } from "@/lib/auth"; diff --git a/src/app/(site)/help/tickets/page.tsx b/src/app/(site)/help/tickets/page.tsx index 9a06b75b9b..1812f6ab08 100644 --- a/src/app/(site)/help/tickets/page.tsx +++ b/src/app/(site)/help/tickets/page.tsx @@ -1,9 +1,9 @@ import { desc, eq } from "drizzle-orm"; -import Link from "next/link"; import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { CSSProperties } from "react"; import { createTicket } from "@/actions/help-tickets"; +import Link from "@/components/link"; import { ContentCard, EmptyState, StatBlock } from "@/components/public/ui"; import { auth } from "@/lib/auth"; import { db, WebsiteHelpCenterTickets } from "@/lib/db"; diff --git a/src/app/(site)/leaderboard/page.tsx b/src/app/(site)/leaderboard/page.tsx index ad6cbc3097..77724944f4 100644 --- a/src/app/(site)/leaderboard/page.tsx +++ b/src/app/(site)/leaderboard/page.tsx @@ -1,6 +1,6 @@ import { desc, eq } from "drizzle-orm"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { ContentCard, EmptyState, RankBadge } from "@/components/public/ui"; import { CurrencyIcon, diff --git a/src/app/(site)/login/page.tsx b/src/app/(site)/login/page.tsx index 02dea8b8fc..40bbb392dd 100644 --- a/src/app/(site)/login/page.tsx +++ b/src/app/(site)/login/page.tsx @@ -1,11 +1,11 @@ import { count, desc, eq } from "drizzle-orm"; import { headers } from "next/headers"; import Image from "next/image"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; 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"; diff --git a/src/app/(site)/me/page.tsx b/src/app/(site)/me/page.tsx index 7aee9cca0c..72927846c3 100644 --- a/src/app/(site)/me/page.tsx +++ b/src/app/(site)/me/page.tsx @@ -1,9 +1,9 @@ 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 Link from "@/components/link"; import { Reveal } from "@/components/motion-reveal"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; @@ -282,7 +282,6 @@ export default async function MePage({ /> + {children} ); diff --git a/src/app/error.tsx b/src/app/error.tsx index 0daaaa6e72..e85e19d2fb 100644 --- a/src/app/error.tsx +++ b/src/app/error.tsx @@ -1,10 +1,10 @@ "use client"; import { Home, RefreshCw } from "lucide-react"; -import Link from "next/link"; import { useTranslations } from "next-intl"; import { useEffect } from "react"; import { ErrorScreen } from "@/components/error-screen"; +import Link from "@/components/link"; /** * Route-segment error boundary. Renders inside the root layout (so the shell diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 293bc9d324..63e8efd62b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -86,7 +86,6 @@ export default async function RootLayout({ nonce={nonce} /> - {nitroUrl?.startsWith("http") ? ( ) : null} diff --git a/src/app/mod/layout.tsx b/src/app/mod/layout.tsx index 1fdff78de6..3694be4d56 100644 --- a/src/app/mod/layout.tsx +++ b/src/app/mod/layout.tsx @@ -9,11 +9,11 @@ import { UserRound, Users, } from "lucide-react"; -import Link from "next/link"; import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import type { ReactNode } from "react"; import { LanguageSwitcher } from "@/components/language-switcher"; +import Link from "@/components/link"; import { ThemeSwitcher } from "@/components/theme-switcher"; import { requireMod } from "@/lib/admin/guard"; import { db, User } from "@/lib/db"; diff --git a/src/app/mod/page.tsx b/src/app/mod/page.tsx index 1b8c211bec..a05edc7069 100644 --- a/src/app/mod/page.tsx +++ b/src/app/mod/page.tsx @@ -8,9 +8,9 @@ import { Ticket, Users, } from "lucide-react"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { StatsCard } from "@/components/admin/stats-card"; +import Link from "@/components/link"; import { getMinStaffRank } from "@/lib/admin/min-staff-rank"; import { Ban as BanTable, diff --git a/src/app/mod/team/page.tsx b/src/app/mod/team/page.tsx index 00cb6e259d..6fdc04cc97 100644 --- a/src/app/mod/team/page.tsx +++ b/src/app/mod/team/page.tsx @@ -1,5 +1,5 @@ import { and, count, desc, gte, inArray, like } from "drizzle-orm"; -import Link from "next/link"; +import Link from "@/components/link"; import { AvatarImage } from "@/components/shared/avatar-image"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; diff --git a/src/app/mod/tickets/page.tsx b/src/app/mod/tickets/page.tsx index f470b471b5..9ececc41eb 100644 --- a/src/app/mod/tickets/page.tsx +++ b/src/app/mod/tickets/page.tsx @@ -1,7 +1,7 @@ -import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { StatusCard } from "@/components/admin/dashboard"; import { UnifiedTicketsTable } from "@/components/admin/unified-tickets-table"; +import Link from "@/components/link"; import { requireModPermission } from "@/lib/admin/guard"; import { fetchUnifiedTicketInbox, diff --git a/src/app/mod/users/[id]/page.tsx b/src/app/mod/users/[id]/page.tsx index 6ccac10767..1661392ba8 100644 --- a/src/app/mod/users/[id]/page.tsx +++ b/src/app/mod/users/[id]/page.tsx @@ -1,7 +1,7 @@ import { eq } from "drizzle-orm"; -import Link from "next/link"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { Badge } from "@/components/ui/badge"; import { requireModPermission } from "@/lib/admin/guard"; import { db, User } from "@/lib/db"; diff --git a/src/app/mod/users/page.tsx b/src/app/mod/users/page.tsx index dd8a2e4ddd..e3bcda42aa 100644 --- a/src/app/mod/users/page.tsx +++ b/src/app/mod/users/page.tsx @@ -1,7 +1,7 @@ import { and, asc, count, desc, eq, like, or, type SQL } from "drizzle-orm"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { DataTable } from "@/components/admin/data-table"; +import Link from "@/components/link"; import { Badge } from "@/components/ui/badge"; import { requireModPermission } from "@/lib/admin/guard"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 94e50873de..70db402072 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,7 +1,7 @@ import { Home, Newspaper } from "lucide-react"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { ErrorScreen } from "@/components/error-screen"; +import Link from "@/components/link"; export default async function NotFound() { const t = await getTranslations("pages.notFound"); diff --git a/src/components/admin/admin-breadcrumb.tsx b/src/components/admin/admin-breadcrumb.tsx index 399c8d1a4e..1979f7aa87 100644 --- a/src/components/admin/admin-breadcrumb.tsx +++ b/src/components/admin/admin-breadcrumb.tsx @@ -1,6 +1,6 @@ import { ChevronRight, Home } from "lucide-react"; -import Link from "next/link"; import { Fragment } from "react"; +import Link from "@/components/link"; export type { BreadcrumbItem } from "@/types/common"; diff --git a/src/components/admin/admin-nav-link.tsx b/src/components/admin/admin-nav-link.tsx index 3f63ba0af2..2f49b84d5d 100644 --- a/src/components/admin/admin-nav-link.tsx +++ b/src/components/admin/admin-nav-link.tsx @@ -1,9 +1,9 @@ "use client"; -import Link from "next/link"; import { usePathname } from "next/navigation"; import type { ReactNode } from "react"; import { useAdminMobile } from "@/components/admin/admin-mobile-wrapper"; +import Link from "@/components/link"; /** * Sidebar link that highlights when its route is active. diff --git a/src/components/admin/admin-section-tabs.tsx b/src/components/admin/admin-section-tabs.tsx index a5dd3bf2d5..deef62c519 100644 --- a/src/components/admin/admin-section-tabs.tsx +++ b/src/components/admin/admin-section-tabs.tsx @@ -1,8 +1,8 @@ "use client"; -import Link from "next/link"; import { usePathname } from "next/navigation"; import { useTranslations } from "next-intl"; +import Link from "@/components/link"; import { cn } from "@/lib/utils"; export interface AdminTab { diff --git a/src/components/admin/admin-simple-pager.tsx b/src/components/admin/admin-simple-pager.tsx index cbd73d1514..b0980d9ae4 100644 --- a/src/components/admin/admin-simple-pager.tsx +++ b/src/components/admin/admin-simple-pager.tsx @@ -1,4 +1,4 @@ -import Link from "next/link"; +import Link from "@/components/link"; /** Minimal prev/next pager for admin list pages without DataTable. */ export function AdminSimplePager({ diff --git a/src/components/admin/article-card.tsx b/src/components/admin/article-card.tsx index 65ce1215d9..464534fe42 100644 --- a/src/components/admin/article-card.tsx +++ b/src/components/admin/article-card.tsx @@ -1,11 +1,11 @@ "use client"; -import Link from "next/link"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useTransition } from "react"; import { deleteArticle } from "@/actions/admin-articles"; import { useConfirmDialog } from "@/components/admin/confirm-dialog"; +import Link from "@/components/link"; export function ArticleCard({ id, diff --git a/src/components/admin/catalog-manager/inline-editor.tsx b/src/components/admin/catalog-manager/inline-editor.tsx index 42b13d61a7..8d8fec9092 100644 --- a/src/components/admin/catalog-manager/inline-editor.tsx +++ b/src/components/admin/catalog-manager/inline-editor.tsx @@ -11,7 +11,6 @@ import { Save, Settings, } from "lucide-react"; -import Link from "next/link"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import { updateCatalogPage } from "@/actions/catalog"; @@ -27,6 +26,7 @@ import { IconPicker } from "@/components/admin/catalog/icon-picker"; import { ItemsShopPreview } from "@/components/admin/catalog/items-shop-preview"; import { QuickAddFurni } from "@/components/admin/catalog/quick-add-furni"; import { CatalogIcon, LAYOUT_COLORS } from "@/components/admin/catalog-tree"; +import Link from "@/components/link"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; diff --git a/src/components/admin/catalog-tree.tsx b/src/components/admin/catalog-tree.tsx index 5e371ad43a..a09db55e03 100644 --- a/src/components/admin/catalog-tree.tsx +++ b/src/components/admin/catalog-tree.tsx @@ -21,7 +21,6 @@ import { Trash2, X, } from "lucide-react"; -import Link from "next/link"; import { useRouter } from "next/navigation"; import { createContext, @@ -33,6 +32,7 @@ import { } from "react"; import { toast } from "sonner"; import { useConfirmDialog } from "@/components/admin/confirm-dialog"; +import Link from "@/components/link"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { diff --git a/src/components/admin/stats-card.tsx b/src/components/admin/stats-card.tsx index 08b1405844..a84255fb02 100644 --- a/src/components/admin/stats-card.tsx +++ b/src/components/admin/stats-card.tsx @@ -1,5 +1,5 @@ import { Minus, TrendingDown, TrendingUp } from "lucide-react"; -import Link from "next/link"; +import Link from "@/components/link"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; interface StatsCardProps { diff --git a/src/components/admin/ticket-queue-banner.tsx b/src/components/admin/ticket-queue-banner.tsx index d62058c923..7264badd95 100644 --- a/src/components/admin/ticket-queue-banner.tsx +++ b/src/components/admin/ticket-queue-banner.tsx @@ -1,4 +1,4 @@ -import Link from "next/link"; +import Link from "@/components/link"; /** Shared clarity banner for dual ticket products (CMS desk vs help-center). */ export function TicketQueueBanner({ diff --git a/src/components/admin/unified-tickets-table.tsx b/src/components/admin/unified-tickets-table.tsx index dfd16fd332..991d777b55 100644 --- a/src/components/admin/unified-tickets-table.tsx +++ b/src/components/admin/unified-tickets-table.tsx @@ -1,8 +1,8 @@ "use client"; -import Link from "next/link"; import { useTranslations } from "next-intl"; import { DataTable } from "@/components/admin/data-table"; +import Link from "@/components/link"; import { Badge } from "@/components/ui/badge"; import type { TicketInboxRow } from "@/lib/admin/ticket-inbox"; import type { DataTableColumn, PaginatedResult } from "@/types/common"; diff --git a/src/components/auth/login-form.tsx b/src/components/auth/login-form.tsx index f76d78017d..e293e94d28 100644 --- a/src/components/auth/login-form.tsx +++ b/src/components/auth/login-form.tsx @@ -1,6 +1,5 @@ "use client"; -import Link from "next/link"; import { signIn } from "next-auth/react"; import { useTranslations } from "next-intl"; import { type FormEvent, useState } from "react"; @@ -10,6 +9,7 @@ import { CaptchaWidget, readCaptchaToken, } from "@/components/auth/captcha-widget"; +import Link from "@/components/link"; import { signInWithTransientRetry } from "@/lib/auth/sign-in-retry"; export function LoginForm({ diff --git a/src/components/auth/register-form.tsx b/src/components/auth/register-form.tsx index 398bf968b5..69be714f77 100644 --- a/src/components/auth/register-form.tsx +++ b/src/components/auth/register-form.tsx @@ -1,12 +1,12 @@ "use client"; import Image from "next/image"; -import Link from "next/link"; import Script from "next/script"; import { signIn } from "next-auth/react"; import { useTranslations } from "next-intl"; import { useActionState, useEffect, useRef, useState } from "react"; import { type RegisterState, register } from "@/actions/register"; +import Link from "@/components/link"; import { signInWithTransientRetry } from "@/lib/auth/sign-in-retry"; interface RegisterFormProps { diff --git a/src/components/link.tsx b/src/components/link.tsx new file mode 100644 index 0000000000..88053aeb6a --- /dev/null +++ b/src/components/link.tsx @@ -0,0 +1,6 @@ +import NextLink from "next/link"; +import type { ComponentProps } from "react"; + +export default function Link(props: ComponentProps) { + return ; +} diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 9da9d14fd2..95b65ead28 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -1,9 +1,9 @@ import { count, eq } from "drizzle-orm"; import Image from "next/image"; -import Link from "next/link"; import type { Session } from "next-auth"; import { getTranslations } from "next-intl/server"; import { LanguageSwitcher } from "@/components/language-switcher"; +import Link from "@/components/link"; import { MobileNav } from "@/components/mobile-nav"; import { NavDropdown } from "@/components/nav-dropdown"; import { NavbarColorPicker } from "@/components/navbar-color-picker"; diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index 6ca95772fe..4fcfe59b92 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -1,7 +1,7 @@ import { count, eq } from "drizzle-orm"; import Image from "next/image"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; +import Link from "@/components/link"; import { cached } from "@/lib/cache"; import { db, User } from "@/lib/db"; import { resolveHotelName } from "@/lib/hotel-name"; diff --git a/src/components/surface-card.tsx b/src/components/surface-card.tsx index 6782784f19..19c2722f53 100644 --- a/src/components/surface-card.tsx +++ b/src/components/surface-card.tsx @@ -1,6 +1,6 @@ import Image from "next/image"; -import Link from "next/link"; import type { CSSProperties, ReactNode } from "react"; +import Link from "@/components/link"; const CARD_BORDER = "color-mix(in srgb, var(--color-text-muted) 12%, transparent)"; diff --git a/src/components/top-header.tsx b/src/components/top-header.tsx index 646a7a67db..6fb3ceb9eb 100644 --- a/src/components/top-header.tsx +++ b/src/components/top-header.tsx @@ -1,9 +1,9 @@ import { count, eq, inArray } from "drizzle-orm"; import Image from "next/image"; -import Link from "next/link"; import type { Session } from "next-auth"; import { getTranslations } from "next-intl/server"; import { HeaderUserIdentity } from "@/components/header-user-identity"; +import Link from "@/components/link"; import { RoomQuickEntry } from "@/components/room-quick-entry"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; import { signOut } from "@/lib/auth"; diff --git a/src/features/housekeeping/foundation/shell/context-nav.tsx b/src/features/housekeeping/foundation/shell/context-nav.tsx index 46926fb00a..d89baae135 100644 --- a/src/features/housekeeping/foundation/shell/context-nav.tsx +++ b/src/features/housekeeping/foundation/shell/context-nav.tsx @@ -1,4 +1,4 @@ -import Link from "next/link"; +import Link from "@/components/link"; interface ContextNavProps { items: readonly { id: string; href: string; label: string }[]; diff --git a/src/features/housekeeping/foundation/shell/domain-rail.tsx b/src/features/housekeeping/foundation/shell/domain-rail.tsx index 9bcdc5a6cc..da85cc3c21 100644 --- a/src/features/housekeeping/foundation/shell/domain-rail.tsx +++ b/src/features/housekeeping/foundation/shell/domain-rail.tsx @@ -1,5 +1,5 @@ import { FileText, Gem, Hotel, Inbox, Settings, Users } from "lucide-react"; -import Link from "next/link"; +import Link from "@/components/link"; import type { HousekeepingNavigationDomain } from "../navigation"; interface DomainRailProps { diff --git a/src/features/housekeeping/foundation/shell/housekeeping-shell.tsx b/src/features/housekeeping/foundation/shell/housekeeping-shell.tsx index 921db4ce40..4c8b6dfea5 100644 --- a/src/features/housekeeping/foundation/shell/housekeeping-shell.tsx +++ b/src/features/housekeeping/foundation/shell/housekeeping-shell.tsx @@ -1,5 +1,5 @@ -import Link from "next/link"; import type { ReactNode } from "react"; +import Link from "@/components/link"; import type { HousekeepingDomainId } from "../../migration/types"; import type { HousekeepingActor } from "../contracts"; import type { HousekeepingNavigationDomain } from "../navigation"; From ca59a1065fd547ea4f851d4397a3d58e6b017137 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 29 Aug 2026 19:27:27 +0200 Subject: [PATCH 2/4] perf: use lzma-wasm for SWF decompression and drop vite Replace the pure-JS lzma decoder with lzma-wasm (Rust/WASM, base64-inlined, zero-alloc decompress), giving an order-of-magnitude speedup on furni imports. Remove the obsolete lzma type shim and the redundant top-level vite dev dependency, which nothing imports directly. --- next.config.ts | 2 +- package.json | 3 +-- pnpm-lock.yaml | 17 +++++++---------- src/lib/services/swf/swf-parser.test.ts | 7 +++++-- src/lib/services/swf/swf-parser.ts | 7 +++++-- src/types/lzma.d.ts | 14 -------------- 6 files changed, 19 insertions(+), 31 deletions(-) delete mode 100644 src/types/lzma.d.ts diff --git a/next.config.ts b/next.config.ts index 1d3953c089..618d221ba0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -32,7 +32,7 @@ const nextConfig: NextConfig = { reactStrictMode: true, compress: true, productionBrowserSourceMaps: false, - serverExternalPackages: ["lzma", "sharp", "pino", "pino-pretty"], + serverExternalPackages: ["lzma-wasm", "sharp", "pino", "pino-pretty"], async redirects() { return [ diff --git a/package.json b/package.json index a3af572b8b..eb68cf6183 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "jszip": "3.10.1", "lenis": "1.3.26", "lucide-react": "1.33.0", - "lzma": "^2.3.2", + "lzma-wasm": "1.0.7", "motion": "13.1.1", "music-metadata": "11.15.0", "mysql2": "3.23.4", @@ -85,7 +85,6 @@ "tailwindcss": "4.3.3", "tsx": "4.23.12", "typescript": "7.0.2", - "vite": "8.2.2", "vitest": "4.1.11" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34fdd9f764..f69623d9e9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -79,9 +79,9 @@ importers: lucide-react: specifier: 1.33.0 version: 1.33.0(react@19.2.8) - lzma: - specifier: ^2.3.2 - version: 2.3.2 + lzma-wasm: + specifier: 1.0.7 + version: 1.0.7 motion: specifier: 13.1.1 version: 13.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -185,9 +185,6 @@ importers: typescript: specifier: 7.0.2 version: 7.0.2 - vite: - specifier: 8.2.2 - version: 8.2.2(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0) vitest: specifier: 4.1.11 version: 4.1.11(@types/node@26.2.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.2.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.12)(yaml@2.9.0)) @@ -2450,9 +2447,9 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - lzma@2.3.2: - resolution: {integrity: sha512-DcfiawQ1avYbW+hsILhF38IKAlnguc/fjHrychs9hdxe4qLykvhT5VTGNs5YRWgaNePh7NTxGD4uv4gKsRomCQ==} - hasBin: true + lzma-wasm@1.0.7: + resolution: {integrity: sha512-/fm+kRC7MqF9aCAWU5GwmgWllzM7ehC46aB5Va7V1PAnUl0qSU8ZA2XWk8yOgZbkgL6MWLDyYIVJR+UQziwvUA==} + engines: {node: '>=16.0.0'} magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -4593,7 +4590,7 @@ snapshots: dependencies: react: 19.2.8 - lzma@2.3.2: {} + lzma-wasm@1.0.7: {} magic-string@0.30.21: dependencies: diff --git a/src/lib/services/swf/swf-parser.test.ts b/src/lib/services/swf/swf-parser.test.ts index 25ef890f47..ef6d2630de 100644 --- a/src/lib/services/swf/swf-parser.test.ts +++ b/src/lib/services/swf/swf-parser.test.ts @@ -1,5 +1,5 @@ import { deflateSync } from "node:zlib"; -import LZMA from "lzma"; +import { initWasmSync, compress as lzmaCompress } from "lzma-wasm"; import { describe, expect, it } from "vitest"; import { decompressSwf, @@ -33,7 +33,10 @@ function cwsFixture(payload: Buffer, version = 6): Buffer { } function zwsFixture(payload: Buffer, version = 6): Buffer { - const rawLzma = LZMA.compress(payload, 9); + initWasmSync(); + const rawLzma = Buffer.from( + lzmaCompress(payload, { format: "lzma", level: 9 }), + ); const props = Buffer.from(rawLzma.slice(0, 5)); const compressedPayload = Buffer.from(rawLzma.slice(13)); const compressedSize = 5 + compressedPayload.length; diff --git a/src/lib/services/swf/swf-parser.ts b/src/lib/services/swf/swf-parser.ts index 8a640b7e93..8ca3a02e94 100644 --- a/src/lib/services/swf/swf-parser.ts +++ b/src/lib/services/swf/swf-parser.ts @@ -3,8 +3,9 @@ */ import { inflateSync } from "node:zlib"; +import { initWasmSync, decompress as lzmaDecompress } from "lzma-wasm"; -const LZMA = require("lzma/src/lzma_worker").LZMA_WORKER; +initWasmSync(); // ── SWF Tag Types ────────────────────────────────────────────────── export const TAG_END = 0; @@ -62,7 +63,9 @@ export function decompressSwf(buffer: Buffer): Buffer { lzmaHeader.writeUInt32LE(0, 9); const lzmaStream = Buffer.concat([lzmaHeader, compressedPayload]); - const decompressed = Buffer.from(LZMA.decompress(lzmaStream) as number[]); + const decompressed = Buffer.from( + lzmaDecompress(lzmaStream, { expectedSize: uncompressedSize }), + ); const header = Buffer.alloc(8); buffer.copy(header, 0, 0, 8); diff --git a/src/types/lzma.d.ts b/src/types/lzma.d.ts deleted file mode 100644 index 502b42f8f9..0000000000 --- a/src/types/lzma.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -declare module "lzma" { - export const LZMA: any; - export function decompress( - byte_array: any, - on_finish?: (result: any, error: any) => void, - on_progress?: (percent: number) => void, - ): any; - export function compress( - string: string | Uint8Array, - mode: number, - on_finish?: (result: any, error: any) => void, - on_progress?: (percent: number) => void, - ): any; -} From 7f39ba425758a5388f358c317e21b8449aa87ed6 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 29 Aug 2026 20:54:06 +0200 Subject: [PATCH 3/4] fix: harden SSO ticket flow and revoke tickets on logout Reuse the outstanding auth_ticket instead of minting a fresh one on every /client load, so reloading the page or opening a second tab no longer invalidates a game session that is still connecting. New tickets are minted with a guard against the previously-read value so concurrent launches converge on the same ticket. Revoke the auth_ticket when signing out (toolbar, header and sign-out everywhere) so a leaked ticket can no longer be replayed against the emulator, and prevent SSO leakage via referral by setting no-referrer on the client iframe. Strip all whitespace from the ticket prefix and build the launch URL through a tested helper that handles query strings, existing sso params and URL fragments correctly. --- src/actions/sessions.ts | 27 +++++++++- src/app/client/client-view.tsx | 10 ++-- src/components/top-header.tsx | 9 +--- src/lib/auth/sso-ticket.test.ts | 92 +++++++++++++++++++++++++++++---- src/lib/auth/sso-ticket.ts | 52 +++++++++++++++++-- src/lib/client-url.test.ts | 49 ++++++++++++++++++ src/lib/client-url.ts | 20 +++++++ 7 files changed, 231 insertions(+), 28 deletions(-) create mode 100644 src/lib/client-url.test.ts create mode 100644 src/lib/client-url.ts diff --git a/src/actions/sessions.ts b/src/actions/sessions.ts index 33b5fceeb5..a43f7acad2 100644 --- a/src/actions/sessions.ts +++ b/src/actions/sessions.ts @@ -9,7 +9,8 @@ import { logger } from "@/lib/logger"; /** * Invalidate every CMS JWT for the signed-in user by bumping website_jwt_version, - * revoke personal access tokens, then end the current browser session too. + * revoke personal access tokens, revoke the game SSO ticket, then end the + * current browser session too. */ export async function signOutEverywhere(): Promise { const session = await auth(); @@ -24,6 +25,7 @@ export async function signOutEverywhere(): Promise { .update(User) .set({ websiteJwtVersion: sql`${User.websiteJwtVersion} + 1`, + authTicket: "", }) .where(eq(User.id, userId)); await invalidateJwtVersionCache(userId); @@ -57,3 +59,26 @@ export async function signOutEverywhere(): Promise { await signOut({ redirectTo: "/login?signedOutAll=1" }); } + +/** + * Log the current user out of the website AND revoke their game SSO ticket. + * + * Without revoking it, a ticket leaked via logs/history/referrers stays valid + * for the emulator after logout. Clearing the ticket makes any future client + * connection with it invalid. + */ +export async function signOutAndRevokeTicket(): Promise { + const session = await auth(); + const userId = Number(session?.user?.id); + if (Number.isInteger(userId) && userId > 0) { + try { + await db.update(User).set({ authTicket: "" }).where(eq(User.id, userId)); + } catch (err) { + logger.warn("Failed to revoke SSO ticket during sign out", { + userId, + error: err instanceof Error ? err.message : "Unknown", + }); + } + } + await signOut({ redirectTo: "/" }); +} diff --git a/src/app/client/client-view.tsx b/src/app/client/client-view.tsx index 99cd04532d..d84ffa094b 100644 --- a/src/app/client/client-view.tsx +++ b/src/app/client/client-view.tsx @@ -1,7 +1,6 @@ "use client"; import { LogOut } from "lucide-react"; -import { signOut } from "next-auth/react"; import { type ReactNode, useCallback, @@ -10,7 +9,9 @@ import { useRef, useState, } from "react"; +import { signOutAndRevokeTicket } from "@/actions/sessions"; import Link from "@/components/link"; +import { buildClientLoginUrl } from "@/lib/client-url"; function ToolbarBtn({ onClick, @@ -94,9 +95,7 @@ export function ClientView({ return () => document.removeEventListener("fullscreenchange", onChange); }, []); - const base = clientUrl.replace(/(\?|&)sso=[^&]*/, ""); - const sep = base.includes("?") ? "&" : "?"; - const clientSrc = `${base}${sep}sso=${encodeURIComponent(ticket)}`; + const clientSrc = buildClientLoginUrl(clientUrl, ticket); const toolbarRef = useRef(null); const dragRef = useRef({ startX: 0, startY: 0, startTop: 0, startLeft: 0 }); @@ -361,7 +360,7 @@ export function ClientView({ signOut({ callbackUrl: "/" })} + onClick={() => void signOutAndRevokeTicket()} title="Logout" > @@ -382,6 +381,7 @@ export function ClientView({ }`} title={hotelName} allow="autoplay; gamepad; fullscreen" + referrerPolicy="no-referrer" loading="eager" /> diff --git a/src/components/top-header.tsx b/src/components/top-header.tsx index 6fb3ceb9eb..240014afc2 100644 --- a/src/components/top-header.tsx +++ b/src/components/top-header.tsx @@ -2,11 +2,11 @@ import { count, eq, inArray } from "drizzle-orm"; import Image from "next/image"; import type { Session } from "next-auth"; import { getTranslations } from "next-intl/server"; +import { signOutAndRevokeTicket } from "@/actions/sessions"; import { HeaderUserIdentity } from "@/components/header-user-identity"; import Link from "@/components/link"; import { RoomQuickEntry } from "@/components/room-quick-entry"; import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail"; -import { signOut } from "@/lib/auth"; import { cached } from "@/lib/cache"; import { db, @@ -405,12 +405,7 @@ export async function TopHeader({ session }: { session: Session | null }) { "color-mix(in srgb, var(--color-text-muted) 12%, transparent)", }} /> -
{ - "use server"; - await signOut({ redirectTo: "/" }); - }} - > + - - {password.length > 0 && ( -
-
- {[1, 2, 3, 4, 5].map((i) => ( -
- ))} -
-

- {strength.label} -

-
- )} + {t("credentials")} + +
-
- -
- - + {t("password")} + +
+ setPassword(e.target.value)} + className="input-glow w-full rounded-xl border-2 px-4 py-3 text-sm font-medium transition-all focus:outline-none" + style={{ + backgroundColor: "var(--color-background)", + color: "var(--color-text-readable)", + borderColor: + "color-mix(in srgb, var(--color-text-muted) 25%, transparent)", + }} + required + /> + +
+ {password.length > 0 && ( +
+
+ {[1, 2, 3, 4, 5].map((i) => ( +
+ ))} +
+

+ {strength.label} +

+
+ )} +
+
+ +
+ + +
{/* Terms */}
-
+
-

- * Required for account creation -

+ + + + {termsError || !termsAccepted ? t("termsRequired") : t("accepted")} +
+ {termsError && ( +

+ {t("termsRequired")} +

+ )} {/* Captcha */} - {showCaptcha && captcha.provider === "turnstile" && ( -
- )} - {showCaptcha && captcha.provider === "recaptcha" && ( -
- )} - {showCaptcha && captcha.provider === "hcaptcha" && ( -
+ {showCaptcha && ( +
+ {captcha.provider === "turnstile" && ( +
+ )} + {captcha.provider === "recaptcha" && ( +
+ )} + {captcha.provider === "hcaptcha" && ( +
+ )} +
)} {/* Submit */}