From cc158d3819a59e73803c4ff902be3316c633811c Mon Sep 17 00:00:00 2001 From: adevopg Date: Mon, 13 Jul 2026 09:54:58 +0000 Subject: [PATCH] web-next: migrar toda la UI al tema real nw-ryu Reescritura completa del frontend Next.js del sistema visual Tailwind "simulado" al tema Django original (nw-ryu), para paridad pixel con la web actual antes del cutover. - Tema real: copia de static/nw-themes/nw-ryu + favicons a public/, el layout carga el novavow-style.css real de ultimo (gana la cascada sobre Tailwind) + Font Awesome. - Shell replicando los partials Django: SiteHeader, Video, Social, Footer, ServerClock; home con estructura real (main-page/middle-content/...). - Helpers reutilizables: PageShell (main-page > middle-content > body-content > title-content) y ServiceBox (title-box-content + back-to-account). - Paginas migradas a clases reales del tema (fieldset/tool-button/char-box/ item-box/info-box-light/max-center-table/alert-message/botones reales), eliminando el markup Tailwind (.nw-btn/.nw-card/.nw-input): auth (login/register/recover/reset/select-account/activate), cuenta + servicios de personaje (revive/unstuck/rename/customize/ change-race/change-faction/level-up/gold/transfer + pago Stripe), ajustes (change-password/change-email/security-token), comunidad (vote-points/recruit/battlepay), foro completo, y admin (indice + 7 secciones + los Admin*Manager). - Se conserva el bilingue (next-intl); claves nuevas en messages/es|en.json. Verificado: typecheck + build OK; rutas protegidas 307->login; sin MISSING_MESSAGE; cero Tailwind residual (solo .nw-tool-btn/.nw-page, clases propias en globals.css). Co-Authored-By: Claude Opus 4.8 (1M context) --- web-next/app/[locale]/account/page.tsx | 293 +- .../activate-account/ActivateClient.tsx | 12 +- .../app/[locale]/activate-account/page.tsx | 14 +- web-next/app/[locale]/admin/forum/page.tsx | 15 +- web-next/app/[locale]/admin/gold/page.tsx | 15 +- web-next/app/[locale]/admin/news/page.tsx | 15 +- web-next/app/[locale]/admin/page.tsx | 44 +- web-next/app/[locale]/admin/prices/page.tsx | 15 +- web-next/app/[locale]/admin/recruit/page.tsx | 15 +- web-next/app/[locale]/admin/users/page.tsx | 15 +- web-next/app/[locale]/admin/votes/page.tsx | 15 +- .../app/[locale]/battlepay-success/page.tsx | 34 +- web-next/app/[locale]/battlepay/page.tsx | 15 +- .../[locale]/change-email/ChangeEmailForm.tsx | 42 +- web-next/app/[locale]/change-email/page.tsx | 11 +- .../change-password/ChangePasswordForm.tsx | 40 +- .../app/[locale]/change-password/page.tsx | 11 +- .../app/[locale]/confirm-new-email/page.tsx | 12 +- .../app/[locale]/confirm-old-email/page.tsx | 12 +- .../app/[locale]/forum/[forumId]/page.tsx | 81 +- web-next/app/[locale]/forum/page.tsx | 85 +- web-next/app/[locale]/forum/search/page.tsx | 83 +- .../[locale]/forum/topic/[topicId]/page.tsx | 121 +- .../[locale]/forum/user/[username]/page.tsx | 107 +- web-next/app/[locale]/gold/page.tsx | 11 +- web-next/app/[locale]/layout.tsx | 28 +- web-next/app/[locale]/login/LoginForm.tsx | 107 +- web-next/app/[locale]/login/page.tsx | 26 +- web-next/app/[locale]/page.tsx | 222 +- web-next/app/[locale]/recover/RecoverForm.tsx | 56 +- web-next/app/[locale]/recover/page.tsx | 15 +- web-next/app/[locale]/recruit/page.tsx | 38 +- .../app/[locale]/register/RegisterForm.tsx | 77 +- web-next/app/[locale]/register/page.tsx | 17 +- web-next/app/[locale]/revive/page.tsx | 11 +- .../security-token/SecurityTokenForm.tsx | 33 +- web-next/app/[locale]/security-token/page.tsx | 11 +- .../select-account/SelectAccountForm.tsx | 33 +- web-next/app/[locale]/select-account/page.tsx | 37 +- .../app/[locale]/service-success/page.tsx | 24 +- web-next/app/[locale]/transfer/page.tsx | 11 +- web-next/app/[locale]/unstuck/page.tsx | 11 +- web-next/app/[locale]/vote-points/page.tsx | 15 +- web-next/app/globals.css | 71 +- web-next/components/AdminForumManager.tsx | 119 +- web-next/components/AdminGoldManager.tsx | 50 +- web-next/components/AdminNewsManager.tsx | 49 +- web-next/components/AdminPricesManager.tsx | 34 +- web-next/components/AdminRecruitManager.tsx | 78 +- web-next/components/AdminUsersManager.tsx | 79 +- web-next/components/AdminVotesManager.tsx | 50 +- web-next/components/BattlepayList.tsx | 54 +- web-next/components/CharacterActionForm.tsx | 32 +- web-next/components/ConfirmClient.tsx | 12 +- web-next/components/Footer.tsx | 108 +- web-next/components/ForumSearchBox.tsx | 9 +- web-next/components/GoldForm.tsx | 20 +- web-next/components/Header.tsx | 55 +- web-next/components/NewTopicForm.tsx | 19 +- web-next/components/PageShell.tsx | 25 + web-next/components/Pagination.tsx | 21 +- web-next/components/PaidServiceForm.tsx | 30 +- web-next/components/PostActions.tsx | 46 +- web-next/components/RecruitClaim.tsx | 70 +- web-next/components/ReplyForm.tsx | 9 +- web-next/components/RichTextArea.tsx | 47 +- web-next/components/ServerClock.tsx | 19 + web-next/components/ServiceBox.tsx | 31 + web-next/components/ServicePageContent.tsx | 20 +- web-next/components/SiteHeader.tsx | 164 ++ web-next/components/Social.tsx | 66 + web-next/components/TopicModBar.tsx | 44 +- web-next/components/TransferForm.tsx | 20 +- web-next/components/Video.tsx | 34 + web-next/components/VotePanel.tsx | 57 +- web-next/messages/en.json | 36 +- web-next/messages/es.json | 36 +- web-next/public/android-chrome-192x192.png | Bin 0 -> 18496 bytes web-next/public/apple-touch-icon.png | Bin 0 -> 12046 bytes web-next/public/browserconfig.xml | 12 + web-next/public/favicon-16x16.png | Bin 0 -> 662 bytes web-next/public/favicon-194x194.png | Bin 0 -> 19744 bytes web-next/public/favicon-32x32.png | Bin 0 -> 1359 bytes web-next/public/favicon.ico | Bin 0 -> 15086 bytes web-next/public/mstile-144x144.png | Bin 0 -> 2014 bytes .../nw-themes/nw-ryu/nw-css/novawow-style.css | 2384 +++++++++++++++++ .../nw-ryu/nw-font/FuturaEF-Book.woff | Bin 0 -> 25108 bytes .../nw-images/nw-avatars/default-avatar.webp | Bin 0 -> 1912 bytes .../nw-classes/death-knight-chest.webp | Bin 0 -> 84068 bytes .../nw-classes/death-knight-medium.jpg | Bin 0 -> 1149 bytes .../nw-images/nw-classes/druid-chest.webp | Bin 0 -> 75569 bytes .../nw-images/nw-classes/druid-medium.jpg | Bin 0 -> 1043 bytes .../nw-images/nw-classes/hunter-chest.webp | Bin 0 -> 92837 bytes .../nw-images/nw-classes/hunter-medium.jpg | Bin 0 -> 1073 bytes .../nw-images/nw-classes/mage-chest.webp | Bin 0 -> 86959 bytes .../nw-images/nw-classes/mage-medium.jpg | Bin 0 -> 1163 bytes .../nw-images/nw-classes/paladin-chest.webp | Bin 0 -> 86411 bytes .../nw-images/nw-classes/paladin-medium.jpg | Bin 0 -> 1235 bytes .../nw-images/nw-classes/priest-chest.webp | Bin 0 -> 84460 bytes .../nw-images/nw-classes/priest-medium.jpg | Bin 0 -> 1118 bytes .../nw-images/nw-classes/rogue-chest.webp | Bin 0 -> 77234 bytes .../nw-images/nw-classes/rogue-medium.jpg | Bin 0 -> 1163 bytes .../nw-images/nw-classes/shaman-chest.webp | Bin 0 -> 93400 bytes .../nw-images/nw-classes/shaman-medium.jpg | Bin 0 -> 1279 bytes .../nw-images/nw-classes/warlock-chest.webp | Bin 0 -> 77994 bytes .../nw-images/nw-classes/warlock-medium.jpg | Bin 0 -> 1166 bytes .../nw-images/nw-classes/warrior-chest.webp | Bin 0 -> 118851 bytes .../nw-images/nw-classes/warrior-medium.jpg | Bin 0 -> 1043 bytes .../nw-classes/wow-death-knight.webp | Bin 0 -> 3570 bytes .../nw-ryu/nw-images/nw-classes/wow-druid.png | Bin 0 -> 6491 bytes .../nw-images/nw-classes/wow-hunter.webp | Bin 0 -> 2724 bytes .../nw-ryu/nw-images/nw-classes/wow-mage.webp | Bin 0 -> 3998 bytes .../nw-images/nw-classes/wow-paladin.webp | Bin 0 -> 3076 bytes .../nw-images/nw-classes/wow-priest.webp | Bin 0 -> 2664 bytes .../nw-images/nw-classes/wow-rogue.webp | Bin 0 -> 3308 bytes .../nw-images/nw-classes/wow-shaman.webp | Bin 0 -> 4082 bytes .../nw-images/nw-classes/wow-warlock.webp | Bin 0 -> 3522 bytes .../nw-images/nw-classes/wow-warrior.webp | Bin 0 -> 3080 bytes .../nw-ryu/nw-images/nw-country-flags/ar.webp | Bin 0 -> 338 bytes .../nw-ryu/nw-images/nw-country-flags/bo.webp | Bin 0 -> 314 bytes .../nw-ryu/nw-images/nw-country-flags/cl.webp | Bin 0 -> 310 bytes .../nw-ryu/nw-images/nw-country-flags/co.webp | Bin 0 -> 342 bytes .../nw-ryu/nw-images/nw-country-flags/cr.webp | Bin 0 -> 332 bytes .../nw-ryu/nw-images/nw-country-flags/ec.webp | Bin 0 -> 354 bytes .../nw-ryu/nw-images/nw-country-flags/gt.webp | Bin 0 -> 294 bytes .../nw-ryu/nw-images/nw-country-flags/mx.png | Bin 0 -> 441 bytes .../nw-ryu/nw-images/nw-country-flags/pa.png | Bin 0 -> 402 bytes .../nw-ryu/nw-images/nw-country-flags/pe.webp | Bin 0 -> 222 bytes .../nw-ryu/nw-images/nw-country-flags/py.webp | Bin 0 -> 326 bytes .../nw-ryu/nw-images/nw-country-flags/uy.webp | Bin 0 -> 364 bytes .../nw-general/account-settings.webp | Bin 0 -> 17602 bytes .../nw-general/novawow-maintenance.webp | Bin 0 -> 562812 bytes .../nw-images/nw-general/nw-divider-mini.webp | Bin 0 -> 760 bytes .../nw-images/nw-general/nw-divider.png | Bin 0 -> 2186 bytes .../nw-images/nw-icons/account-icons.webp | Bin 0 -> 176042 bytes .../nw-icons/achievement-level-80.jpg | Bin 0 -> 1275 bytes .../nw-images/nw-icons/alliance-icon.jpg | Bin 0 -> 1356 bytes .../nw-icons/alliance-no-border.webp | Bin 0 -> 7196 bytes .../nw-icons/death-knight-medium.jpg | Bin 0 -> 1149 bytes .../nw-images/nw-icons/druid-medium.jpg | Bin 0 -> 1043 bytes .../nw-ryu/nw-images/nw-icons/horde-icon.jpg | Bin 0 -> 1247 bytes .../nw-images/nw-icons/horde-no-border.webp | Bin 0 -> 3632 bytes .../nw-images/nw-icons/hunter-medium.jpg | Bin 0 -> 1073 bytes .../nw-ryu/nw-images/nw-icons/mage-medium.jpg | Bin 0 -> 1163 bytes .../nw-images/nw-icons/money-copper.gif | Bin 0 -> 234 bytes .../nw-ryu/nw-images/nw-icons/money-gold.gif | Bin 0 -> 244 bytes .../nw-images/nw-icons/money-silver.webp | Bin 0 -> 196 bytes .../nw-images/nw-icons/paladin-medium.jpg | Bin 0 -> 1235 bytes .../nw-images/nw-icons/priest-medium.jpg | Bin 0 -> 1118 bytes .../nw-ryu/nw-images/nw-icons/refresh.webp | Bin 0 -> 786 bytes .../nw-images/nw-icons/rogue-medium.jpg | Bin 0 -> 1163 bytes .../nw-images/nw-icons/shaman-medium.jpg | Bin 0 -> 1279 bytes .../nw-images/nw-icons/warlock-medium.jpg | Bin 0 -> 1166 bytes .../nw-images/nw-icons/warrior-medium.jpg | Bin 0 -> 1043 bytes .../nw-images/nw-logos/bancolombia-logo.webp | Bin 0 -> 4120 bytes .../nw-images/nw-logos/discord-logo.webp | Bin 0 -> 548 bytes .../nw-images/nw-logos/dlocalgo-logo.webp | Bin 0 -> 574 bytes .../nw-ryu/nw-images/nw-logos/facebook.png | Bin 0 -> 395179 bytes .../nw-ryu/nw-images/nw-logos/nequi-logo.webp | Bin 0 -> 3126 bytes .../novawow-main-logo-transparent.webp | Bin 0 -> 138 bytes .../nw-ryu/nw-images/nw-logos/nw-logo.webp | Bin 0 -> 1858 bytes .../nw-images/nw-logos/nw-long-logo.webp | Bin 0 -> 6602 bytes .../nw-images/nw-logos/oficina-peru.png | Bin 0 -> 264251 bytes .../nw-images/nw-logos/paypal-logo.webp | Bin 0 -> 10408 bytes .../nw-images/nw-logos/paypal-p-logo.webp | Bin 0 -> 606 bytes .../nw-ryu/nw-images/nw-logos/plin-peru.webp | Bin 0 -> 122828 bytes .../nw-images/nw-logos/skrill-logo-small.webp | Bin 0 -> 230 bytes .../nw-images/nw-logos/skrill-logo.webp | Bin 0 -> 1358 bytes .../nw-ryu/nw-images/nw-logos/twitch.png | Bin 0 -> 463189 bytes .../nw-images/nw-logos/whatsapp-logo.webp | Bin 0 -> 682 bytes .../nw-ryu/nw-images/nw-logos/yape-peru.png | Bin 0 -> 170459 bytes .../nw-ryu/nw-images/nw-logos/youtube.webp | Bin 0 -> 208226 bytes .../nw-images/nw-mails/facebook-logo-gray.png | Bin 0 -> 605 bytes .../nw-mails/instagram-logo-gray.png | Bin 0 -> 640 bytes .../nw-images/nw-mails/nw-mail-body.jpg | Bin 0 -> 84498 bytes .../nw-images/nw-mails/nw-mail-footer.jpg | Bin 0 -> 28394 bytes .../nw-images/nw-mails/nw-mail-logo.webp | Bin 0 -> 177412 bytes .../nw-images/nw-mails/twitter-logo-gray.png | Bin 0 -> 602 bytes .../nw-images/nw-mails/youtube-logo-gray.png | Bin 0 -> 402 bytes .../nw-races/big-blood-elf-female.webp | Bin 0 -> 6216 bytes .../nw-races/big-blood-elf-male.webp | Bin 0 -> 6338 bytes .../nw-races/big-draenei-female.webp | Bin 0 -> 5548 bytes .../nw-images/nw-races/big-draenei-male.webp | Bin 0 -> 6062 bytes .../nw-images/nw-races/big-dwarf-female.webp | Bin 0 -> 5358 bytes .../nw-images/nw-races/big-dwarf-male.webp | Bin 0 -> 5378 bytes .../nw-images/nw-races/big-gnome-female.webp | Bin 0 -> 5784 bytes .../nw-images/nw-races/big-gnome-male.webp | Bin 0 -> 5692 bytes .../nw-images/nw-races/big-human-female.webp | Bin 0 -> 5644 bytes .../nw-images/nw-races/big-human-male.webp | Bin 0 -> 5894 bytes .../nw-races/big-night-elf-female.webp | Bin 0 -> 6276 bytes .../nw-races/big-night-elf-male.webp | Bin 0 -> 6166 bytes .../nw-images/nw-races/big-orc-female.webp | Bin 0 -> 5900 bytes .../nw-images/nw-races/big-orc-male.webp | Bin 0 -> 6324 bytes .../nw-images/nw-races/big-tauren-female.webp | Bin 0 -> 5396 bytes .../nw-images/nw-races/big-tauren-male.webp | Bin 0 -> 5958 bytes .../nw-images/nw-races/big-troll-female.webp | Bin 0 -> 5932 bytes .../nw-images/nw-races/big-troll-male.webp | Bin 0 -> 6940 bytes .../nw-images/nw-races/big-undead-female.webp | Bin 0 -> 4890 bytes .../nw-images/nw-races/big-undead-male.webp | Bin 0 -> 5536 bytes .../nw-images/nw-ranks/10_Proficient.svg | 18 + .../nw-images/nw-ranks/11_Experienced.svg | 13 + .../nw-ryu/nw-images/nw-ranks/12_Mentor.svg | 13 + .../nw-ryu/nw-images/nw-ranks/13_Veteran.svg | 13 + .../nw-images/nw-ranks/14_GrandMaster.svg | 13 + .../nw-ryu/nw-images/nw-ranks/1_Newbie.svg | 13 + .../nw-ryu/nw-images/nw-ranks/2_Rookie.svg | 13 + .../nw-images/nw-ranks/3_Apprentice.svg | 13 + .../nw-ryu/nw-images/nw-ranks/4_Explorer.svg | 13 + .../nw-images/nw-ranks/5_Contributor.svg | 13 + .../nw-images/nw-ranks/6_Enthusiast.svg | 13 + .../nw-images/nw-ranks/7_Collaborator.svg | 13 + .../nw-ryu/nw-images/nw-ranks/8_Regular.svg | 13 + .../nw-images/nw-ranks/9_RisingStar.svg | 18 + .../nw-images/nw-vote-sites/arenatop100.webp | Bin 0 -> 9086 bytes .../nw-images/nw-vote-sites/gtop100.jpg | Bin 0 -> 2425 bytes .../nw-images/nw-vote-sites/top100arena.jpg | Bin 0 -> 2283 bytes .../nw-ryu/nw-images/nw-vote-sites/topg.webp | Bin 0 -> 3840 bytes .../nw-ryu/nw-js-handlers/create_check.js | 1883 +++++++++++++ .../nw-js-handlers/my_account_response.js | 1 + .../nw-js-handlers/promo_code_response.js | 59 + .../nw-ryu/nw-js-handlers/recruit-response.js | 62 + .../nw-ryu/nw-js-handlers/rename_response.js | 75 + .../nw-js-handlers/store_novawow_response.js | 91 + .../nw-ryu/nw-js-handlers/store_response.js | 28 + .../nw-js-handlers/trade_points_response.js | 4 + .../nw-themes/nw-ryu/nw-js/nw-scripts.js | 35 + .../public/nw-themes/nw-ryu/nw-js/power.js | 871 ++++++ .../nw-themes/nw-ryu/nw-videos/nw-logo.mp4 | Bin 0 -> 12092315 bytes web-next/public/safari-pinned-tab.svg | 49 + web-next/public/site.webmanifest | 20 + 230 files changed, 7899 insertions(+), 1386 deletions(-) create mode 100644 web-next/components/PageShell.tsx create mode 100644 web-next/components/ServerClock.tsx create mode 100644 web-next/components/ServiceBox.tsx create mode 100644 web-next/components/SiteHeader.tsx create mode 100644 web-next/components/Social.tsx create mode 100644 web-next/components/Video.tsx create mode 100644 web-next/public/android-chrome-192x192.png create mode 100644 web-next/public/apple-touch-icon.png create mode 100644 web-next/public/browserconfig.xml create mode 100644 web-next/public/favicon-16x16.png create mode 100644 web-next/public/favicon-194x194.png create mode 100644 web-next/public/favicon-32x32.png create mode 100644 web-next/public/favicon.ico create mode 100644 web-next/public/mstile-144x144.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-css/novawow-style.css create mode 100644 web-next/public/nw-themes/nw-ryu/nw-font/FuturaEF-Book.woff create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-avatars/default-avatar.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/death-knight-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/death-knight-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/druid-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/druid-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/hunter-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/hunter-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/mage-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/mage-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/paladin-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/paladin-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/priest-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/priest-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/rogue-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/rogue-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/shaman-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/shaman-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/warlock-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/warlock-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/warrior-chest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/warrior-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-death-knight.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-druid.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-hunter.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-mage.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-paladin.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-priest.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-rogue.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-shaman.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-warlock.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-classes/wow-warrior.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/ar.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/bo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/cl.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/co.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/cr.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/ec.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/gt.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/mx.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/pa.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/pe.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/py.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-country-flags/uy.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-general/account-settings.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-general/novawow-maintenance.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-general/nw-divider-mini.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-general/nw-divider.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/account-icons.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/achievement-level-80.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/alliance-icon.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/alliance-no-border.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/death-knight-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/druid-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/horde-icon.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/horde-no-border.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/hunter-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/mage-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/money-copper.gif create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/money-gold.gif create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/money-silver.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/paladin-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/priest-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/refresh.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/rogue-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/shaman-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/warlock-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-icons/warrior-medium.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/bancolombia-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/discord-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/dlocalgo-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/facebook.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/nequi-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/novawow-main-logo-transparent.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/nw-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/nw-long-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/oficina-peru.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/paypal-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/paypal-p-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/plin-peru.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/skrill-logo-small.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/skrill-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/twitch.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/whatsapp-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/yape-peru.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/youtube.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-mails/facebook-logo-gray.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-mails/instagram-logo-gray.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-footer.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-logo.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-mails/twitter-logo-gray.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-mails/youtube-logo-gray.png create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-blood-elf-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-blood-elf-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-draenei-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-draenei-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-dwarf-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-dwarf-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-gnome-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-gnome-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-human-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-human-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-night-elf-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-night-elf-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-orc-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-orc-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-tauren-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-tauren-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-troll-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-troll-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-undead-female.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-races/big-undead-male.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/10_Proficient.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/11_Experienced.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/12_Mentor.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/13_Veteran.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/14_GrandMaster.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/1_Newbie.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/2_Rookie.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/3_Apprentice.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/4_Explorer.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/5_Contributor.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/6_Enthusiast.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/7_Collaborator.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/8_Regular.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-ranks/9_RisingStar.svg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-vote-sites/arenatop100.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-vote-sites/gtop100.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-vote-sites/top100arena.jpg create mode 100644 web-next/public/nw-themes/nw-ryu/nw-images/nw-vote-sites/topg.webp create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js-handlers/create_check.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js-handlers/my_account_response.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js-handlers/promo_code_response.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js-handlers/recruit-response.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js-handlers/rename_response.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js-handlers/store_novawow_response.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js-handlers/store_response.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js-handlers/trade_points_response.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js/nw-scripts.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-js/power.js create mode 100644 web-next/public/nw-themes/nw-ryu/nw-videos/nw-logo.mp4 create mode 100644 web-next/public/safari-pinned-tab.svg create mode 100644 web-next/public/site.webmanifest diff --git a/web-next/app/[locale]/account/page.tsx b/web-next/app/[locale]/account/page.tsx index 53b7e29..9b72eef 100644 --- a/web-next/app/[locale]/account/page.tsx +++ b/web-next/app/[locale]/account/page.tsx @@ -3,10 +3,13 @@ import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { getAccountDashboard } from '@/lib/account' import { isAdmin } from '@/lib/admin' -import { LogoutButton } from '@/components/LogoutButton' +import { PageShell } from '@/components/PageShell' export const dynamic = 'force-dynamic' +// Grupos de servicios con su icono del sprite real (acc-icon ). +type ToolItem = { href: string; icon: string; label: string; desc: string } + export default async function AccountPage({ params }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) @@ -19,148 +22,186 @@ export default async function AccountPage({ params }: { params: Promise<{ locale const data = await getAccountDashboard(session) const admin = await isAdmin(session) - const card = 'nw-card' - - return ( -
-
-

{t('title')}

- -
- - {admin && ( - - 🛡️ {t('adminPanel')} - - - )} - -
-
-
-

{t('bnetAccount')}: {data.bnetEmail}

-

{t('gameAccount')}: {data.gameAccount}

-

- {t('securityToken')}:{' '} - - {data.securityTokenDate - ? `${t('requested')} — ${new Date(data.securityTokenDate).toLocaleString(locale)}` - : t('notRequested')} - -

-
-
- -
-

{t('points')}

-
-

{t('dp')}: {data.dp}

-

{t('vp')}: {data.vp}

-

{t('battlepayCredits')}: {data.battlepayCredits}

-
-
-
- -
-

{t('characters')}

- {data.characters.length === 0 ? ( -

{t('noCharacters')}

- ) : ( -
- {data.characters.map((c) => ( -
- {/* eslint-disable-next-line @next/next/no-img-element */} - {c.raceName} -
-

{c.name}

-

- {t('level')} {c.level} · {c.raceName} {c.className} -

-

- 📍 - {c.zone} -

-

- {c.gold}g {c.silver}s {c.copper}c -

-
-
- ))} -
- )} -
- - -
- ) -} - -function ServicesPanel({ t }: { t: (k: string) => string }) { - const groups: { title: string; items: { href: string; label: string }[] }[] = [ + const groups: { title: string; items: ToolItem[] }[] = [ + { + title: t('accountSettings'), + items: [ + { href: '/change-password', icon: 'password-icon', label: t('svcChangePassword'), desc: t('svcChangePasswordDesc') }, + { href: '/change-email', icon: 'email-icon', label: t('svcChangeEmail'), desc: t('svcChangeEmailDesc') }, + { href: '/security-token', icon: 'token-icon', label: t('svcToken'), desc: t('svcTokenDesc') }, + ], + }, { title: t('servicesTitle'), items: [ - { href: '/rename', label: t('svcRename') }, - { href: '/customize', label: t('svcCustomize') }, - { href: '/change-race', label: t('svcChangeRace') }, - { href: '/change-faction', label: t('svcChangeFaction') }, - { href: '/level-up', label: t('svcLevelUp') }, - { href: '/gold', label: t('svcGold') }, - { href: '/transfer', label: t('svcTransfer') }, + { href: '/rename', icon: 'rename-icon', label: t('svcRename'), desc: t('svcRenameDesc') }, + { href: '/customize', icon: 'customize-icon', label: t('svcCustomize'), desc: t('svcCustomizeDesc') }, + { href: '/change-race', icon: 'change-race-icon', label: t('svcChangeRace'), desc: t('svcChangeRaceDesc') }, + { href: '/change-faction', icon: 'change-faction-icon', label: t('svcChangeFaction'), desc: t('svcChangeFactionDesc') }, + { href: '/level-up', icon: 'level-up-icon', label: t('svcLevelUp'), desc: t('svcLevelUpDesc') }, + { href: '/gold', icon: 'gold-icon', label: t('svcGold'), desc: t('svcGoldDesc') }, + { href: '/transfer', icon: 'tranfer-char-icon', label: t('svcTransfer'), desc: t('svcTransferDesc') }, ], }, { title: t('utilitiesTitle'), items: [ - { href: '/revive', label: t('svcRevive') }, - { href: '/unstuck', label: t('svcUnstuck') }, - { href: '/security-token', label: t('svcToken') }, - ], - }, - { - title: t('accountSettings'), - items: [ - { href: '/change-password', label: t('svcChangePassword') }, - { href: '/change-email', label: t('svcChangeEmail') }, + { href: '/revive', icon: 'revive-icon', label: t('svcRevive'), desc: t('svcReviveDesc') }, + { href: '/unstuck', icon: 'unstuck-icon', label: t('svcUnstuck'), desc: t('svcUnstuckDesc') }, ], }, { title: t('communityTitle'), items: [ - { href: '/vote-points', label: t('svcVote') }, - { href: '/recruit', label: t('svcRecruit') }, - { href: '/battlepay', label: t('svcStore') }, + { href: '/vote-points', icon: 'vote-icon', label: t('svcVote'), desc: t('svcVoteDesc') }, + { href: '/recruit', icon: 'recruit-a-friend-icon', label: t('svcRecruit'), desc: t('svcRecruitDesc') }, + { href: '/battlepay', icon: 'store-icon', label: t('svcStore'), desc: t('svcStoreDesc') }, ], }, ] return ( -
- {groups.map((g) => ( -
-

{g.title}

-
- {g.items.map((it) => ( - - {it.label} - - - ))} -
-
- ))} -
+ + {/* Información de la cuenta */} +
+
+

{t('infoTitle')}

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{t('bnetAccount')}{data.bnetEmail}
{t('gameAccount')}{data.gameAccount}
{t('dp')}{data.dp}
{t('vp')}{data.vp}
{t('battlepayCredits')}{data.battlepayCredits}
{t('securityToken')} + {data.securityTokenDate ? ( + + {t('requested')} — {new Date(data.securityTokenDate).toLocaleString(locale)} + + ) : ( + {t('notRequested')} + )} +
+
+
+ + {/* Personajes */} +
+
+

{t('characters')}

+
+
+ {data.characters.length === 0 ? ( +

{t('noCharacters')}

+ ) : ( + data.characters.map((c) => ( +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {c.raceName} + {c.name} +
+ + {t('level')} {c.level} · {c.raceName} {c.className} + +
+ 📍 {c.zone} +
+ + {c.gold} + {/* eslint-disable-next-line @next/next/no-img-element */} + g {c.silver} + {/* eslint-disable-next-line @next/next/no-img-element */} + s {c.copper} + {/* eslint-disable-next-line @next/next/no-img-element */} + c + +
+
+ )) + )} +
+
+ + {/* Utilidades y Herramientas */} +
+
+

{t('toolsTitle')}

+
+
+ {admin && ( + +
+
+
+ 🛡️ {t('adminPanel')} +
+ {t('adminPanelDesc')} +
+
+ + )} + {groups.map((g) => ( +
+
+

{g.title}

+
+ + + {rows(g.items).map((pair, i) => ( + + {pair.map((it) => ( + + ))} + {pair.length === 1 && + ))} + +
+ +
+
+
+ {it.label} +
+ {it.desc} +
+
+ +
} +
+
+ ))} +
+
+
) } + +// Agrupa los items de dos en dos (2 columnas, como el my-account de Django). +function rows(items: T[]): T[][] { + const out: T[][] = [] + for (let i = 0; i < items.length; i += 2) out.push(items.slice(i, i + 2)) + return out +} diff --git a/web-next/app/[locale]/activate-account/ActivateClient.tsx b/web-next/app/[locale]/activate-account/ActivateClient.tsx index 2536a42..c1612c5 100644 --- a/web-next/app/[locale]/activate-account/ActivateClient.tsx +++ b/web-next/app/[locale]/activate-account/ActivateClient.tsx @@ -37,19 +37,17 @@ export function ActivateClient({ hash }: { hash: string }) { }, [hash]) return ( -
+
{state === 'loading' &&

{t('activating')}

} {state === 'ok' && ( <> -

{t('success')}

-

- - {t('goLogin')} - + {t('success')} +

+ {t('goLogin')}

)} - {state === 'error' &&

{t(errorKey)}

} + {state === 'error' && {t(errorKey)}}
) } diff --git a/web-next/app/[locale]/activate-account/page.tsx b/web-next/app/[locale]/activate-account/page.tsx index 59467b8..761e4ef 100644 --- a/web-next/app/[locale]/activate-account/page.tsx +++ b/web-next/app/[locale]/activate-account/page.tsx @@ -1,4 +1,5 @@ -import { setRequestLocale } from 'next-intl/server' +import { getTranslations, setRequestLocale } from 'next-intl/server' +import { PageShell } from '@/components/PageShell' import { ActivateClient } from './ActivateClient' export default async function ActivatePage({ @@ -11,10 +12,15 @@ export default async function ActivatePage({ const { locale } = await params setRequestLocale(locale) const { act } = await searchParams + const t = await getTranslations('Activate') return ( -
- -
+ +
+
+ +
+
+
) } diff --git a/web-next/app/[locale]/admin/forum/page.tsx b/web-next/app/[locale]/admin/forum/page.tsx index 26a9bd2..2b51c38 100644 --- a/web-next/app/[locale]/admin/forum/page.tsx +++ b/web-next/app/[locale]/admin/forum/page.tsx @@ -1,8 +1,9 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' -import { redirect } from '@/i18n/navigation' +import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { isAdmin } from '@/lib/admin' import { listCategoriesWithForums } from '@/lib/admin-forum' +import { PageShell } from '@/components/PageShell' import { AdminForumManager } from '@/components/AdminForumManager' export const dynamic = 'force-dynamic' @@ -16,9 +17,13 @@ export default async function AdminForumPage({ params }: { params: Promise<{ loc if (!(await isAdmin(session))) redirect({ href: '/', locale }) const groups = await listCategoriesWithForums() return ( -
-

{t('forum')}

- -
+ +
+
+ {t('backToPanel')} + +
+
+
) } diff --git a/web-next/app/[locale]/admin/gold/page.tsx b/web-next/app/[locale]/admin/gold/page.tsx index a81c9c8..b2ced8b 100644 --- a/web-next/app/[locale]/admin/gold/page.tsx +++ b/web-next/app/[locale]/admin/gold/page.tsx @@ -1,8 +1,9 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' -import { redirect } from '@/i18n/navigation' +import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { isAdmin } from '@/lib/admin' import { listGoldOptions } from '@/lib/admin-gold' +import { PageShell } from '@/components/PageShell' import { AdminGoldManager } from '@/components/AdminGoldManager' export const dynamic = 'force-dynamic' @@ -16,9 +17,13 @@ export default async function AdminGoldPage({ params }: { params: Promise<{ loca if (!(await isAdmin(session))) redirect({ href: '/', locale }) const options = await listGoldOptions() return ( -
-

{t('gold')}

- -
+ +
+
+ {t('backToPanel')} + +
+
+
) } diff --git a/web-next/app/[locale]/admin/news/page.tsx b/web-next/app/[locale]/admin/news/page.tsx index d824b23..711dd5c 100644 --- a/web-next/app/[locale]/admin/news/page.tsx +++ b/web-next/app/[locale]/admin/news/page.tsx @@ -1,8 +1,9 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' -import { redirect } from '@/i18n/navigation' +import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { isAdmin } from '@/lib/admin' import { listNews } from '@/lib/admin-news' +import { PageShell } from '@/components/PageShell' import { AdminNewsManager } from '@/components/AdminNewsManager' export const dynamic = 'force-dynamic' @@ -16,9 +17,13 @@ export default async function AdminNewsPage({ params }: { params: Promise<{ loca if (!(await isAdmin(session))) redirect({ href: '/', locale }) const news = await listNews() return ( -
-

{t('manageNews')}

- -
+ +
+
+ {t('backToPanel')} + +
+
+
) } diff --git a/web-next/app/[locale]/admin/page.tsx b/web-next/app/[locale]/admin/page.tsx index e910ce6..626059d 100644 --- a/web-next/app/[locale]/admin/page.tsx +++ b/web-next/app/[locale]/admin/page.tsx @@ -2,6 +2,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { isAdmin } from '@/lib/admin' +import { PageShell } from '@/components/PageShell' export const dynamic = 'force-dynamic' @@ -23,22 +24,35 @@ export default async function AdminPage({ params }: { params: Promise<{ locale: { href: '/admin/forum', label: t('manageForum'), icon: '💬' }, ] + const rows: typeof items[] = [] + for (let i = 0; i < items.length; i += 2) rows.push(items.slice(i, i + 2)) + return ( -
-

{t('title')}

-
- {items.map((it) => ( - - {it.icon} - {it.label} - - - ))} + +
+
+ + + {rows.map((pair, i) => ( + + {pair.map((it) => ( + + ))} + {pair.length === 1 && + ))} + +
+ +
+
+ {it.icon} {it.label} +
+
+ +
} +
+
-
+ ) } diff --git a/web-next/app/[locale]/admin/prices/page.tsx b/web-next/app/[locale]/admin/prices/page.tsx index 2e2f494..00e28d4 100644 --- a/web-next/app/[locale]/admin/prices/page.tsx +++ b/web-next/app/[locale]/admin/prices/page.tsx @@ -1,8 +1,9 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' -import { redirect } from '@/i18n/navigation' +import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { isAdmin } from '@/lib/admin' import { getAllPrices } from '@/lib/admin-prices' +import { PageShell } from '@/components/PageShell' import { AdminPricesManager } from '@/components/AdminPricesManager' export const dynamic = 'force-dynamic' @@ -16,9 +17,13 @@ export default async function AdminPricesPage({ params }: { params: Promise<{ lo if (!(await isAdmin(session))) redirect({ href: '/', locale }) const prices = await getAllPrices() return ( -
-

{t('prices')}

- -
+ +
+
+ {t('backToPanel')} + +
+
+
) } diff --git a/web-next/app/[locale]/admin/recruit/page.tsx b/web-next/app/[locale]/admin/recruit/page.tsx index 2467744..704fdc9 100644 --- a/web-next/app/[locale]/admin/recruit/page.tsx +++ b/web-next/app/[locale]/admin/recruit/page.tsx @@ -1,8 +1,9 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' -import { redirect } from '@/i18n/navigation' +import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { isAdmin } from '@/lib/admin' import { listRecruitRewards } from '@/lib/admin-recruit' +import { PageShell } from '@/components/PageShell' import { AdminRecruitManager } from '@/components/AdminRecruitManager' export const dynamic = 'force-dynamic' @@ -16,9 +17,13 @@ export default async function AdminRecruitPage({ params }: { params: Promise<{ l if (!(await isAdmin(session))) redirect({ href: '/', locale }) const rewards = await listRecruitRewards() return ( -
-

{t('recruit')}

- -
+ +
+
+ {t('backToPanel')} + +
+
+
) } diff --git a/web-next/app/[locale]/admin/users/page.tsx b/web-next/app/[locale]/admin/users/page.tsx index f73a459..e439ec0 100644 --- a/web-next/app/[locale]/admin/users/page.tsx +++ b/web-next/app/[locale]/admin/users/page.tsx @@ -1,7 +1,8 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' -import { redirect } from '@/i18n/navigation' +import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { isAdmin } from '@/lib/admin' +import { PageShell } from '@/components/PageShell' import { AdminUsersManager } from '@/components/AdminUsersManager' export const dynamic = 'force-dynamic' @@ -14,9 +15,13 @@ export default async function AdminUsersPage({ params }: { params: Promise<{ loc if (!session.bnetId) redirect({ href: '/login', locale }) if (!(await isAdmin(session))) redirect({ href: '/', locale }) return ( -
-

{t('users')}

- -
+ +
+
+ {t('backToPanel')} + +
+
+
) } diff --git a/web-next/app/[locale]/admin/votes/page.tsx b/web-next/app/[locale]/admin/votes/page.tsx index 3cdcdcb..89a817b 100644 --- a/web-next/app/[locale]/admin/votes/page.tsx +++ b/web-next/app/[locale]/admin/votes/page.tsx @@ -1,8 +1,9 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' -import { redirect } from '@/i18n/navigation' +import { redirect, Link } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { isAdmin } from '@/lib/admin' import { listVoteSites } from '@/lib/admin-votes' +import { PageShell } from '@/components/PageShell' import { AdminVotesManager } from '@/components/AdminVotesManager' export const dynamic = 'force-dynamic' @@ -16,9 +17,13 @@ export default async function AdminVotesPage({ params }: { params: Promise<{ loc if (!(await isAdmin(session))) redirect({ href: '/', locale }) const sites = await listVoteSites() return ( -
-

{t('votes')}

- -
+ +
+
+ {t('backToPanel')} + +
+
+
) } diff --git a/web-next/app/[locale]/battlepay-success/page.tsx b/web-next/app/[locale]/battlepay-success/page.tsx index 7fbfa00..1fed939 100644 --- a/web-next/app/[locale]/battlepay-success/page.tsx +++ b/web-next/app/[locale]/battlepay-success/page.tsx @@ -2,6 +2,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { Link } from '@/i18n/navigation' import { fulfillCheckoutSession } from '@/lib/fulfill' import { isSessionPaid } from '@/lib/stripe' +import { PageShell } from '@/components/PageShell' export const dynamic = 'force-dynamic' @@ -26,20 +27,23 @@ export default async function BattlepaySuccessPage({ } return ( -
-

{t('title')}

- {status === 'paid' ? ( -

{t('paidOk')}

- ) : status === 'already' ? ( -

{t('alreadyPaid')}

- ) : ( -

{t('genericError')}

- )} -

- - ← {t('backToStore')} - -

-
+ +
+
+ {status === 'paid' ? ( + {t('paidOk')} + ) : status === 'already' ? ( + {t('alreadyPaid')} + ) : ( + {t('genericError')} + )} +
+
+

+ ← {t('backToStore')} +

+
+
+
) } diff --git a/web-next/app/[locale]/battlepay/page.tsx b/web-next/app/[locale]/battlepay/page.tsx index 3a84c67..cfa75c8 100644 --- a/web-next/app/[locale]/battlepay/page.tsx +++ b/web-next/app/[locale]/battlepay/page.tsx @@ -2,6 +2,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { redirect } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { getPendingOrders } from '@/lib/battlepay' +import { PageShell } from '@/components/PageShell' import { BattlepayList } from '@/components/BattlepayList' export const dynamic = 'force-dynamic' @@ -17,10 +18,14 @@ export default async function BattlepayPage({ params }: { params: Promise<{ loca const orders = await getPendingOrders(session.accountId ?? 0) return ( -
-

{t('title')}

-

{t('intro')}

- -
+ +
+
+

{t('intro')}

+
+ +
+
+
) } diff --git a/web-next/app/[locale]/change-email/ChangeEmailForm.tsx b/web-next/app/[locale]/change-email/ChangeEmailForm.tsx index 667a51c..930f2ab 100644 --- a/web-next/app/[locale]/change-email/ChangeEmailForm.tsx +++ b/web-next/app/[locale]/change-email/ChangeEmailForm.tsx @@ -40,22 +40,32 @@ export function ChangeEmailForm() { } } - const field = 'nw-input' - return ( -
-

{t('info')}

-
- upd('currentPassword', e.target.value)} className={field} /> - upd('currentEmail', e.target.value)} className={field} /> - upd('newEmail', e.target.value)} className={field} /> - upd('confEmail', e.target.value)} className={field} /> - upd('token', e.target.value)} className={field} /> - -
- {message &&

{message.text}

} -
+ <> +

{t('info')}

+
+
+
+ + + + + + + + + +
upd('currentPassword', e.target.value)} />
upd('currentEmail', e.target.value)} />
upd('newEmail', e.target.value)} />
upd('confEmail', e.target.value)} />
upd('token', e.target.value)} />
+ +
+
+
+
+ {message && {message.text}} +
+
+ ) } diff --git a/web-next/app/[locale]/change-email/page.tsx b/web-next/app/[locale]/change-email/page.tsx index c8968a6..eaebbf2 100644 --- a/web-next/app/[locale]/change-email/page.tsx +++ b/web-next/app/[locale]/change-email/page.tsx @@ -1,6 +1,8 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { redirect } from '@/i18n/navigation' import { getSession } from '@/lib/session' +import { PageShell } from '@/components/PageShell' +import { ServiceBox } from '@/components/ServiceBox' import { ChangeEmailForm } from './ChangeEmailForm' export const dynamic = 'force-dynamic' @@ -13,9 +15,10 @@ export default async function ChangeEmailPage({ params }: { params: Promise<{ lo if (!session.bnetId) redirect({ href: '/login', locale }) if (!session.username) redirect({ href: '/select-account', locale }) return ( -
-

{t('title')}

- -
+ + + + + ) } diff --git a/web-next/app/[locale]/change-password/ChangePasswordForm.tsx b/web-next/app/[locale]/change-password/ChangePasswordForm.tsx index 25bcac2..bd32619 100644 --- a/web-next/app/[locale]/change-password/ChangePasswordForm.tsx +++ b/web-next/app/[locale]/change-password/ChangePasswordForm.tsx @@ -56,21 +56,31 @@ export function ChangePasswordForm() { } } - const field = 'nw-input' - return ( -
-

{t('info')}

-
- upd('currentPassword', e.target.value)} className={field} /> - upd('newPassword', e.target.value)} className={field} /> - upd('confPassword', e.target.value)} className={field} /> - upd('token', e.target.value)} className={field} /> - -
- {message &&

{message.text}

} -
+ <> +

{t('info')}

+
+
+
+ + + + + + + + +
upd('currentPassword', e.target.value)} />
upd('newPassword', e.target.value)} />
upd('confPassword', e.target.value)} />
upd('token', e.target.value)} />
+ +
+
+
+
+ {message && {message.text}} +
+
+ ) } diff --git a/web-next/app/[locale]/change-password/page.tsx b/web-next/app/[locale]/change-password/page.tsx index 3180f23..32f28e1 100644 --- a/web-next/app/[locale]/change-password/page.tsx +++ b/web-next/app/[locale]/change-password/page.tsx @@ -1,6 +1,8 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { redirect } from '@/i18n/navigation' import { getSession } from '@/lib/session' +import { PageShell } from '@/components/PageShell' +import { ServiceBox } from '@/components/ServiceBox' import { ChangePasswordForm } from './ChangePasswordForm' export const dynamic = 'force-dynamic' @@ -14,9 +16,10 @@ export default async function ChangePasswordPage({ params }: { params: Promise<{ if (!session.username) redirect({ href: '/select-account', locale }) return ( -
-

{t('title')}

- -
+ + + + + ) } diff --git a/web-next/app/[locale]/confirm-new-email/page.tsx b/web-next/app/[locale]/confirm-new-email/page.tsx index 6c83814..7b30647 100644 --- a/web-next/app/[locale]/confirm-new-email/page.tsx +++ b/web-next/app/[locale]/confirm-new-email/page.tsx @@ -1,4 +1,5 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' +import { PageShell } from '@/components/PageShell' import { ConfirmClient } from '@/components/ConfirmClient' export default async function Page({ params, searchParams }: { params: Promise<{ locale: string }>; searchParams: Promise<{ hash?: string }> }) { @@ -7,9 +8,12 @@ export default async function Page({ params, searchParams }: { params: Promise<{ const { hash } = await searchParams const t = await getTranslations('ConfirmNewEmail') return ( -
-

{t('title')}

- -
+ +
+
+ +
+
+
) } diff --git a/web-next/app/[locale]/confirm-old-email/page.tsx b/web-next/app/[locale]/confirm-old-email/page.tsx index 532280a..12f6d99 100644 --- a/web-next/app/[locale]/confirm-old-email/page.tsx +++ b/web-next/app/[locale]/confirm-old-email/page.tsx @@ -1,4 +1,5 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' +import { PageShell } from '@/components/PageShell' import { ConfirmClient } from '@/components/ConfirmClient' export default async function Page({ params, searchParams }: { params: Promise<{ locale: string }>; searchParams: Promise<{ hash?: string }> }) { @@ -7,9 +8,12 @@ export default async function Page({ params, searchParams }: { params: Promise<{ const { hash } = await searchParams const t = await getTranslations('ConfirmOldEmail') return ( -
-

{t('title')}

- -
+ +
+
+ +
+
+
) } diff --git a/web-next/app/[locale]/forum/[forumId]/page.tsx b/web-next/app/[locale]/forum/[forumId]/page.tsx index 48de0c3..7aadca8 100644 --- a/web-next/app/[locale]/forum/[forumId]/page.tsx +++ b/web-next/app/[locale]/forum/[forumId]/page.tsx @@ -3,6 +3,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { Link } from '@/i18n/navigation' import { getForum, getTopics, countTopics } from '@/lib/forum' import { getSession } from '@/lib/session' +import { PageShell } from '@/components/PageShell' import { NewTopicForm } from '@/components/NewTopicForm' import { Pagination } from '@/components/Pagination' @@ -32,46 +33,50 @@ export default async function ForumPage({ const canPost = Boolean(session.username) return ( -
-

- - ← {t('backToForum')} - -

-

{forum!.name}

- {forum!.description &&

{forum!.description}

} + +
+
+

+ ← {t('backToForum')} +

+ {forum!.description &&

{forum!.description}

} +
- {topics.length === 0 ? ( -

{t('noTopics')}

- ) : ( -
    - {topics.map((topic) => ( -
  • -
    - - {topic.sticky && [{t('sticky')}]} - {topic.locked && [{t('locked')}]} - {topic.name} - -

    - {t('by')} {topic.poster} -

    -
    -
    - {topic.views} {t('views')} -
    -
  • - ))} -
- )} + {topics.length === 0 ? ( +

{t('noTopics')}

+ ) : ( + + + {topics.map((topic) => ( + + + + + ))} + +
+ + {topic.sticky && [{t('sticky')}] } + {topic.locked && [{t('locked')}] } + {topic.name} + +

+ {t('by')} {topic.poster} +

+
+ {topic.views} {t('views')} +
+ )} - `/forum/${id}?page=${p}`} /> + `/forum/${id}?page=${p}`} /> - {canPost ? ( - - ) : ( -

{t('loginToPost')}

- )} -
+ {canPost ? ( + + ) : ( +

{t('loginToPost')}

+ )} +
+ + ) } diff --git a/web-next/app/[locale]/forum/page.tsx b/web-next/app/[locale]/forum/page.tsx index f566db5..03a601a 100644 --- a/web-next/app/[locale]/forum/page.tsx +++ b/web-next/app/[locale]/forum/page.tsx @@ -1,6 +1,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { Link } from '@/i18n/navigation' import { getForumIndex } from '@/lib/forum' +import { PageShell } from '@/components/PageShell' import { ForumSearchBox } from '@/components/ForumSearchBox' export const dynamic = 'force-dynamic' @@ -12,43 +13,51 @@ export default async function ForumIndexPage({ params }: { params: Promise<{ loc const categories = await getForumIndex() return ( -
-

{t('title')}

- - {categories.length === 0 ? ( -

{t('noForums')}

- ) : ( - categories.map((cat) => ( -
-

{cat.name}

-
    - {cat.forums.map((f) => ( -
  • -
    - - {f.name} - - {f.description &&

    {f.description}

    } -
    -
    -
    - {f.topic_count} {t('topics')} · {f.post_count} {t('posts')} -
    - {f.last_topic_id && ( -
    - - {f.last_topic_name} - {' '} - {t('by')} {f.last_topic_poster} -
    - )} -
    -
  • - ))} -
-
- )) - )} -
+ +
+
+ + {categories.length === 0 ? ( +

{t('noForums')}

+ ) : ( + categories.map((cat) => ( +
+
+

{cat.name}

+
+ + + {cat.forums.map((f) => ( + + + + + ))} + +
+ + {f.name} + + {f.description &&

{f.description}

} +
+ + {f.topic_count} {t('topics')} · {f.post_count} {t('posts')} + + {f.last_topic_id && ( +

+ {f.last_topic_name}{' '} + + {t('by')} {f.last_topic_poster} + +

+ )} +
+
+
+ )) + )} +
+
+
) } diff --git a/web-next/app/[locale]/forum/search/page.tsx b/web-next/app/[locale]/forum/search/page.tsx index f2cd5aa..fde6e6e 100644 --- a/web-next/app/[locale]/forum/search/page.tsx +++ b/web-next/app/[locale]/forum/search/page.tsx @@ -1,6 +1,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { Link } from '@/i18n/navigation' import { searchTopics, countSearchTopics } from '@/lib/forum' +import { PageShell } from '@/components/PageShell' import { ForumSearchBox } from '@/components/ForumSearchBox' export const dynamic = 'force-dynamic' @@ -23,47 +24,47 @@ export default async function ForumSearchPage({ const total = valid ? await countSearchTopics(query) : 0 return ( -
-

- - ← {t('backToForum')} - -

-

{t('search')}

- + +
+
+

+ ← {t('backToForum')} +

+
+ - {!valid ? ( -

{t('searchHint')}

- ) : results.length === 0 ? ( -

{t('noResults', { query })}

- ) : ( - <> -

{t('resultsCount', { count: total, query })}

-
    - {results.map((r) => ( -
  • -
    - - {r.name} - -

    - {t('in')}{' '} - - {r.forum_name} - {' '} - · {t('by')} {r.poster} -

    -
    - {r.updated_at && ( - - {new Date(r.updated_at).toLocaleDateString(locale)} - - )} -
  • - ))} -
- - )} -
+ {!valid ? ( +

{t('searchHint')}

+ ) : results.length === 0 ? ( +

{t('noResults', { query })}

+ ) : ( + <> +

{t('resultsCount', { count: total, query })}

+ + + {results.map((r) => ( + + + {r.updated_at && ( + + )} + + ))} + +
+ + {r.name} + +

+ {t('in')} {r.forum_name} · {t('by')} {r.poster} +

+
+ {new Date(r.updated_at).toLocaleDateString(locale)} +
+ + )} + + + ) } diff --git a/web-next/app/[locale]/forum/topic/[topicId]/page.tsx b/web-next/app/[locale]/forum/topic/[topicId]/page.tsx index 8dc9f1d..7fd4181 100644 --- a/web-next/app/[locale]/forum/topic/[topicId]/page.tsx +++ b/web-next/app/[locale]/forum/topic/[topicId]/page.tsx @@ -4,6 +4,7 @@ import { Link } from '@/i18n/navigation' import { getTopic, getPosts, countPosts, listForumsForMove } from '@/lib/forum' import { getSession } from '@/lib/session' import { forumIsModerator, canEditPost } from '@/lib/forum-perm' +import { PageShell } from '@/components/PageShell' import { ReplyForm } from '@/components/ReplyForm' import { PostActions } from '@/components/PostActions' import { TopicModBar } from '@/components/TopicModBar' @@ -36,73 +37,71 @@ export default async function TopicPage({ const moveForums = isMod ? (await listForumsForMove()).filter((f) => f.id !== topic!.forum_id) : [] const canReply = Boolean(session.username) && (!topic!.locked || isMod) + const title = ( + <> + {topic!.sticky && [{t('pinned')}] } + {topic!.locked && [{t('locked')}] } + {topic!.deleted && [{t('deletedMark')}] } + {topic!.name} + + ) + return ( -
-

- - ← {t('backToForum')} - -

-

- {topic!.sticky && [{t('pinned')}]} - {topic!.locked && [{t('locked')}]} - {topic!.deleted && [{t('deletedMark')}]} - {topic!.name} -

+ +
+
+

+ ← {t('backToForum')} +

+
- {isMod && ( - - )} + {isMod && ( + + )} -
- {posts.map((post) => ( -
-
- - - {post.poster} - - {post.deleted && [{t('deletedMark')}]} - - {post.time && ( - - {new Date(post.time).toLocaleString(locale)} - {post.edited && · {t('editedMark')}} + {posts.map((post) => ( +
+
+ + {post.poster} + {post.deleted && [{t('deletedMark')}]} + {post.time && ( + + {new Date(post.time).toLocaleString(locale)} + {post.edited && · {t('editedMark')}} + + )} +
+
+
+ {canEditPost(session, isMod, post.poster_id) && ( + )}
-
- {canEditPost(session, isMod, post.poster_id) && ( - - )} -
- ))} + ))} + + `/forum/topic/${id}?page=${p}`} /> + + {canReply ? ( + page === totalPages ? ( + + ) : ( +

+ {t('replyOnLastPage')} +

+ ) + ) : ( + !session.username &&

{t('loginToPost')}

+ )} +
- - `/forum/topic/${id}?page=${p}`} /> - - {canReply ? ( - page === totalPages ? ( - - ) : ( -

- - {t('replyOnLastPage')} - -

- ) - ) : ( - !session.username &&

{t('loginToPost')}

- )} -
+ ) } diff --git a/web-next/app/[locale]/forum/user/[username]/page.tsx b/web-next/app/[locale]/forum/user/[username]/page.tsx index bff13fd..8f4111f 100644 --- a/web-next/app/[locale]/forum/user/[username]/page.tsx +++ b/web-next/app/[locale]/forum/user/[username]/page.tsx @@ -1,6 +1,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { Link } from '@/i18n/navigation' import { getForumProfile } from '@/lib/forum' +import { PageShell } from '@/components/PageShell' export const dynamic = 'force-dynamic' @@ -14,55 +15,69 @@ export default async function ForumProfilePage({ const t = await getTranslations('Forum') const p = await getForumProfile(decodeURIComponent(username)) - return ( -
-

- - ← {t('backToForum')} - -

+ const title = ( + <> + {p.username} + {p.status && ( + [{p.status === 'admin' ? t('roleAdmin') : t('roleGm')}] + )} + + ) -
-

- {p.username} - {p.status && ( - - {p.status === 'admin' ? t('roleAdmin') : t('roleGm')} - + return ( + +
+
+

+ ← {t('backToForum')} +

+
+ + + + + + + + +
+ {t('posts')} +
+ {p.postCount} +
+ {t('topics')} +
+ {p.topicCount} +
+ {t('memberSince')} +
+ + {p.joindate ? new Date(p.joindate).toLocaleDateString(locale) : '—'} + +
+ + {p.recentTopics.length > 0 && ( + <> +
+

{t('recentTopics')}

+
+ + + {p.recentTopics.map((topic) => ( + + + + ))} + +
+ + {topic.name} + +
+ )} -

-
-
-

{t('posts')}

-

{p.postCount}

-
-
-

{t('topics')}

-

{p.topicCount}

-
-
-

{t('memberSince')}

-

- {p.joindate ? new Date(p.joindate).toLocaleDateString(locale) : '—'} -

-
- - {p.recentTopics.length > 0 && ( -
-

{t('recentTopics')}

-
    - {p.recentTopics.map((topic) => ( -
  • - - {topic.name} - -
  • - ))} -
-
- )} -
+ ) } diff --git a/web-next/app/[locale]/gold/page.tsx b/web-next/app/[locale]/gold/page.tsx index 9d76a7f..0e16d54 100644 --- a/web-next/app/[locale]/gold/page.tsx +++ b/web-next/app/[locale]/gold/page.tsx @@ -3,6 +3,8 @@ import { redirect } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { getGameCharacters } from '@/lib/characters' import { getGoldOptions } from '@/lib/prices' +import { PageShell } from '@/components/PageShell' +import { ServiceBox } from '@/components/ServiceBox' import { GoldForm } from '@/components/GoldForm' export const dynamic = 'force-dynamic' @@ -18,9 +20,10 @@ export default async function GoldPage({ params }: { params: Promise<{ locale: s const [chars, options] = await Promise.all([getGameCharacters(session.accountId!), getGoldOptions()]) return ( -
-

{t('gold.title')}

- c.name)} options={options} /> -
+ + + c.name)} options={options} /> + + ) } diff --git a/web-next/app/[locale]/layout.tsx b/web-next/app/[locale]/layout.tsx index b86092b..81a6295 100644 --- a/web-next/app/[locale]/layout.tsx +++ b/web-next/app/[locale]/layout.tsx @@ -4,6 +4,8 @@ import { NextIntlClientProvider, hasLocale } from 'next-intl' import { getTranslations, setRequestLocale } from 'next-intl/server' import { routing } from '@/i18n/routing' import { Header } from '@/components/Header' +import { Video } from '@/components/Video' +import { Social } from '@/components/Social' import { Footer } from '@/components/Footer' import '../globals.css' @@ -23,7 +25,7 @@ export async function generateMetadata({ title: t('title'), description: t('description'), openGraph: { - siteName: 'Nova WoW', + siteName: 'NovaWoW', title: t('title'), description: t('description'), type: 'website', @@ -46,10 +48,30 @@ export default async function LocaleLayout({ return ( - + + {/* Iconos del sitio (idénticos a la web Django) */} + + + + + + + + + {/* Font Awesome (mismos iconos que la web Django) */} + + {/* Tema real de NovaWoW (nw-ryu). Va el ÚLTIMO para ganar la cascada. */} + + +
-
{children}
+