From e10f73b12a5ab9d6d43db1a87af1a3f5adb456bb Mon Sep 17 00:00:00 2001 From: adevopg Date: Wed, 15 Jul 2026 11:48:33 +0000 Subject: [PATCH] store: la ruta pasa a ser /store- y /store da 404 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Como el original (/store-bennu). El slug sale de acore_auth.realmlist, así que no puede ser una carpeta fija: lo resuelve la ruta dinámica `[realm]`, que ya servía /-realm y /-players con este mismo patrón. Solo hubo que añadirle el tipo `store`; el contenido de la página se mueve a components/StorePage.tsx. /store da 404 sin código extra: al no existir ya la carpeta estática `store`, entra por `[realm]`, no resuelve y cae en el notFound() que ya estaba. Enlaces actualizados: el de /my-account (AccountTools, que es cliente y recibe el href ya calculado) y el cancelUrl de la pasarela, que devolvía a /store. Verificado: /es/store-trinity y /en/store-trinity dan 200 y la tienda carga el catálogo; /es/store y /en/store dan 404; un slug ajeno (/es/store-bennu) también 404; /es/trinity-realm y /es/trinity-players siguen bien; y el enlace de /my-account apunta a /es/store-trinity. Co-Authored-By: Claude Opus 4.8 (1M context) --- web-next/app/[locale]/[realm]/page.tsx | 18 ++++++++++--- web-next/app/[locale]/my-account/page.tsx | 3 ++- web-next/app/api/store/send/route.ts | 3 ++- web-next/components/AccountTools.tsx | 5 ++-- .../page.tsx => components/StorePage.tsx} | 25 ++++++------------- 5 files changed, 30 insertions(+), 24 deletions(-) rename web-next/{app/[locale]/store/page.tsx => components/StorePage.tsx} (68%) diff --git a/web-next/app/[locale]/[realm]/page.tsx b/web-next/app/[locale]/[realm]/page.tsx index 2574779..1c6140d 100644 --- a/web-next/app/[locale]/[realm]/page.tsx +++ b/web-next/app/[locale]/[realm]/page.tsx @@ -3,31 +3,40 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { notFound } from 'next/navigation' import { getRealmName, realmSlug } from '@/lib/realm' import { RealmInfo } from '@/components/RealmInfo' +import { StorePage } from '@/components/StorePage' import { PlayersBoard } from '@/components/PlayersBoard' export const dynamic = 'force-dynamic' -type Kind = 'realm' | 'players' +type Kind = 'realm' | 'players' | 'store' // Rutas dinámicas del reino (el slug viene de acore_auth.realmlist): // /-realm → información del reino (Rates/Horarios) // /-players → estadísticas de personajes (datos reales de la BD) +// /store- → la tienda (así la nombra el original: /store-bennu) +// Cualquier otro valor cae en notFound(), y por eso /store a secas da 404: al no +// existir ya una ruta estática `store`, entra por aquí y no resuelve. async function resolve(param: string): Promise<{ name: string; kind: Kind } | null> { const name = await getRealmName() const slug = realmSlug(name) if (param === `${slug}-realm`) return { name, kind: 'realm' } if (param === `${slug}-players`) return { name, kind: 'players' } + if (param === `store-${slug}`) return { name, kind: 'store' } return null } export async function generateMetadata({ params, }: { - params: Promise<{ realm: string }> + params: Promise<{ locale: string; realm: string }> }): Promise { const { realm } = await params const r = await resolve(realm) if (!r) return {} + if (r.kind === 'store') { + const t = await getTranslations({ locale: (await params).locale, namespace: 'Store' }) + return { title: `${t('title')} - ${r.name}` } + } return { title: `${r.kind === 'players' ? 'Personajes' : 'Reino'} - ${r.name}` } } @@ -38,9 +47,12 @@ export default async function RealmPage({ }) { const { locale, realm } = await params setRequestLocale(locale) - const t = await getTranslations('Misc') const r = await resolve(realm) if (!r) notFound() + // La tienda trae su propia cabecera y sus comprobaciones de sesión. + if (r.kind === 'store') return + + const t = await getTranslations('Misc') const heading = r.kind === 'players' ? t('realm.charactersHeading') : t('realm.realmHeading') diff --git a/web-next/app/[locale]/my-account/page.tsx b/web-next/app/[locale]/my-account/page.tsx index 3d55038..5191ac6 100644 --- a/web-next/app/[locale]/my-account/page.tsx +++ b/web-next/app/[locale]/my-account/page.tsx @@ -1,6 +1,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' +import { getRealmName, realmSlug } from '@/lib/realm' import { getAccountDashboard, getAccountRank } from '@/lib/account' import { reconcileSumUpCheckouts } from '@/lib/sumup' import { getGameAccounts } from '@/lib/auth' @@ -130,7 +131,7 @@ export default async function AccountPage({ params }: { params: Promise<{ locale

{t('toolsTitle')}

- + ) diff --git a/web-next/app/api/store/send/route.ts b/web-next/app/api/store/send/route.ts index fa94b11..6945e9a 100644 --- a/web-next/app/api/store/send/route.ts +++ b/web-next/app/api/store/send/route.ts @@ -1,6 +1,7 @@ import { randomUUID } from 'crypto' import { getSession } from '@/lib/session' import { getGameCharacters } from '@/lib/characters' +import { getRealmName, realmSlug } from '@/lib/realm' import { priceStoreCart, purchaseStoreCart, storeEuroTotal, createStoreOrder, storeConcept, type StoreCartLine } from '@/lib/store' import { CARD_MIN_EUR } from '@/lib/store-pricing' import { createCheckoutSession } from '@/lib/stripe' @@ -95,7 +96,7 @@ export async function POST(request: Request) { characterName: character, productName, successUrl: `${site}/service-success?service=store`, - cancelUrl: `${site}/${safeLocale(body.locale)}/store`, + cancelUrl: `${site}/${safeLocale(body.locale)}/store-${realmSlug(await getRealmName())}`, metadata, }) if (!r.success || !r.url) return Response.json({ success: false, error: 'stripeError' }) diff --git a/web-next/components/AccountTools.tsx b/web-next/components/AccountTools.tsx index 9c6b3ea..4f65361 100644 --- a/web-next/components/AccountTools.tsx +++ b/web-next/components/AccountTools.tsx @@ -9,7 +9,7 @@ type Tool = { href: string; icon: string; label: string; desc: string; labelClas /** Replica los paneles plegables «OPCIONES DE CUENTA / OPCIONES DE PERSONAJE» * del my-account de Django (acordeón por click), con las herramientas ya * implementadas en Next. */ -export function AccountTools({ isAdmin }: { isAdmin: boolean }) { +export function AccountTools({ isAdmin, storeHref }: { isAdmin: boolean; storeHref: string }) { const t = useTranslations('Account') const accountTools: Tool[] = [ @@ -41,7 +41,8 @@ export function AccountTools({ isAdmin }: { isAdmin: boolean }) { { href: '/transfer-character', icon: 'tranfer-char-icon', label: t('svcTransfer'), desc: t('svcTransferDesc') }, { href: '/restore-character', icon: 'restore-icon', label: t('svcRestoreChar'), desc: t('svcRestoreCharDesc') }, { href: '/restore-items', icon: 'prox-icon', label: t('svcRestoreItems'), desc: t('svcRestoreItemsDesc') }, - { href: '/store', icon: 'store-icon', label: t('svcStoreItems'), desc: t('svcStoreItemsDesc') }, + // La tienda lleva el reino en la URL (/store-): lo calcula el server. + { href: storeHref, icon: 'store-icon', label: t('svcStoreItems'), desc: t('svcStoreItemsDesc') }, { href: '/send-gift', icon: 'send-gift-icon', label: t('svcSendGift'), desc: t('svcSendGiftDesc') }, ] const historyTools: Tool[] = [ diff --git a/web-next/app/[locale]/store/page.tsx b/web-next/components/StorePage.tsx similarity index 68% rename from web-next/app/[locale]/store/page.tsx rename to web-next/components/StorePage.tsx index c265ae6..e1cefe5 100644 --- a/web-next/app/[locale]/store/page.tsx +++ b/web-next/components/StorePage.tsx @@ -1,36 +1,27 @@ -import type { Metadata } from 'next' -import { getTranslations, setRequestLocale } from 'next-intl/server' +import { getTranslations } from 'next-intl/server' import { redirect } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { getGameCharacters } from '@/lib/characters' import { getStoreBalances } from '@/lib/store' -import { getRealmName } from '@/lib/realm' import { wowheadWotlkHome } from '@/lib/wowhead' import { PageShell } from '@/components/PageShell' import { ServiceBox } from '@/components/ServiceBox' import { StoreBrowser } from '@/components/StoreBrowser' import { StoreNews } from '@/components/StoreNews' -export const dynamic = 'force-dynamic' - -export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise { - const { locale } = await params - const t = await getTranslations({ locale, namespace: 'Store' }) - return { title: t('title') } -} - -export default async function StorePage({ params }: { params: Promise<{ locale: string }> }) { - const { locale } = await params - setRequestLocale(locale) - +/** + * Contenido de la tienda. Vive aquí y no en una ruta propia porque la URL lleva + * el reino (`/store-`, como el original) y eso lo resuelve la ruta + * dinámica `[realm]`, igual que /-realm y /-players. + */ +export async function StorePage({ locale, realm }: { locale: string; realm: string }) { const session = await getSession() if (!session.bnetId) redirect({ href: '/log-in', locale }) if (!session.username) redirect({ href: '/select-account', locale }) - const [chars, balances, realm] = await Promise.all([ + const [chars, balances] = await Promise.all([ getGameCharacters(session.accountId!), getStoreBalances(session.accountId!), - getRealmName(), ]) const t = await getTranslations('Store')