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/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/(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} ); @@ -100,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 }); @@ -367,7 +360,7 @@ export function ClientView({ signOut({ callbackUrl: "/" })} + onClick={() => void signOutAndRevokeTicket()} title="Logout" > @@ -388,7 +381,9 @@ export function ClientView({ }`} title={hotelName} allow="autoplay; gamepad; fullscreen" + referrerPolicy="no-referrer" loading="eager" + fetchPriority={"high" as const} /> diff --git a/src/app/client/loading.tsx b/src/app/client/loading.tsx index 5dfe6a4233..1be8672d27 100644 --- a/src/app/client/loading.tsx +++ b/src/app/client/loading.tsx @@ -1,16 +1,48 @@ +import Image from "next/image"; + export default function ClientLoading() { return (
+
+
+ +
+
+
); } 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/globals.css b/src/app/globals.css index 4e896fa311..39463b0d44 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1701,6 +1701,28 @@ details[open] > summary .details-open\:rotate-180 { .animate-pulse-glow { animation: pulse-glow 2s ease-in-out infinite; } +.animate-shake { + animation: shake 0.45s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; +} +@keyframes shake { + 10%, + 90% { + transform: translateX(-2px); + } + 20%, + 80% { + transform: translateX(4px); + } + 30%, + 50%, + 70% { + transform: translateX(-6px); + } + 40%, + 60% { + transform: translateX(6px); + } +} /* Glass morphism */ .glass { 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..67151cab3c 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 { @@ -47,6 +47,8 @@ export function RegisterForm({ FormData >(register, { error: null, ok: false }); const [termsAccepted, setTermsAccepted] = useState(false); + const [termsError, setTermsError] = useState(false); + const termsRef = useRef(null); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); const [showConfirm, setShowConfirm] = useState(false); @@ -115,11 +117,16 @@ export function RegisterForm({ className="relative overflow-hidden bg-center bg-cover rounded-t-xl" style={{ backgroundImage: "url(/assets/images/mybobba_banner.png)", - height: "180px", - maxHeight: "150px", + height: "160px", }} > -
+
-
- - +
+ + {t("accountDetails")} + +
-
- - +
+
+ + +
+
+ + +
{/* Password & Confirm */} -
-
-