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}
+