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) <noreply@anthropic.com>
This commit is contained in:
+134
-88
@@ -1,7 +1,9 @@
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server'
|
||||
import { Link } from '@/i18n/navigation'
|
||||
import { setRequestLocale } from 'next-intl/server'
|
||||
import { ServerClock } from '@/components/ServerClock'
|
||||
import { getNoticias, getServerStatus, type Noticia, type ServerStatus } from '@/lib/home'
|
||||
|
||||
const SERVER_NAME = 'NovaWoW'
|
||||
|
||||
// SSR por petición: los datos se leen DIRECTAMENTE de MySQL desde el servidor Next.
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
@@ -13,100 +15,144 @@ async function getData(): Promise<{ noticias: Noticia[]; status: ServerStatus |
|
||||
}
|
||||
}
|
||||
|
||||
function formatFecha(iso: string | Date | null): string {
|
||||
if (!iso) return ''
|
||||
const d = new Date(iso)
|
||||
if (isNaN(d.getTime())) return String(iso)
|
||||
return d.toLocaleString('es-ES', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
export default async function HomePage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params
|
||||
setRequestLocale(locale)
|
||||
const t = await getTranslations('Home')
|
||||
const { noticias, status } = await getData()
|
||||
|
||||
const online = status ? status.online_characters : 0
|
||||
const loginStatus = status ? status.status : 'Offline'
|
||||
const loginClass = loginStatus === 'Online' ? 'green-info' : 'red-info'
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Hero */}
|
||||
<section className="relative overflow-hidden border-b border-nw-border/50">
|
||||
<div className="mx-auto max-w-4xl px-4 py-16 text-center sm:py-24">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src="/brand/logo-long.webp" alt="Nova WoW" className="mx-auto mb-4 max-h-28 w-auto drop-shadow-[0_0_25px_rgba(215,150,2,0.35)]" />
|
||||
<p className="mb-3 text-sm font-semibold uppercase tracking-[0.3em] text-nw-gold/80">
|
||||
WotLK Classic 3.4.3
|
||||
</p>
|
||||
<h1 className="nw-heading text-4xl sm:text-5xl">Nova WoW</h1>
|
||||
<p className="mx-auto mt-5 max-w-2xl text-nw-muted">{t('tagline')}</p>
|
||||
<div className="mt-8 flex flex-wrap justify-center gap-3">
|
||||
<Link href="/register" className="nw-btn">
|
||||
{t('ctaRegister')}
|
||||
</Link>
|
||||
<Link href="/forum" className="nw-btn-ghost">
|
||||
{t('ctaForum')}
|
||||
</Link>
|
||||
<div className="main-page">
|
||||
<div className="middle-content middle-content-index">
|
||||
<div className="body-content">
|
||||
<div className="title-content">
|
||||
<h1>{SERVER_NAME} WOTLK 3.4.3</h1>
|
||||
</div>
|
||||
<div className="box-content">
|
||||
<div className="raf-index-holder">
|
||||
<div className="right-content" id="right-content-min">
|
||||
<div className="right-body">
|
||||
{/* Estado del servidor + reloj en vivo (réplica de la isla React de Django) */}
|
||||
<table className="max-center-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="yellow-info">
|
||||
{status?.server_name ?? '—'}{' '}
|
||||
<span className="blue-info small-font">{status?.expansion ?? ''}</span>
|
||||
</td>
|
||||
<td className="real-info-box green-info">{online}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Login</td>
|
||||
<td className={`real-info-box ${loginClass}`}>{loginStatus}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Hora</td>
|
||||
<td className="real-info-box">
|
||||
<ServerClock />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<hr />
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="centered">
|
||||
<td colSpan={2}>
|
||||
<p>
|
||||
<span>{status?.address ?? ''}</span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="raf-index-responsive">
|
||||
<a href="/recruit">¡RECLUTA A UN AMIGO!</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="raf-index">
|
||||
<a href="/recruit">¡RECLUTA A UN AMIGO!</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body-box-content">
|
||||
<div className="body-box-content">
|
||||
<p>
|
||||
{SERVER_NAME} es una comunidad enfocada en usuarios de habla hispana,
|
||||
especialmente dirigida al público español y latino.
|
||||
</p>
|
||||
<p>
|
||||
Atraemos a usuarios apasionados tanto por el PvE como por el PvP, y ofrecemos una
|
||||
amplia variedad de contenido.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Nuestro objetivo principal es mantener una comunidad de alto nivel, brindando un
|
||||
ambiente agradable para que todos los miembros, nuevos y veteranos, encuentren aquí
|
||||
un lugar donde puedan vivir momentos inigualables.
|
||||
</p>
|
||||
<p>¡Únete y disfruta de una experiencia única con miles de usuarios!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div className="title-content-h3">
|
||||
<h3 className="big-font">NOTICIAS</h3>
|
||||
</div>
|
||||
<div className="box-content">
|
||||
<div className="body-box-content">
|
||||
{noticias.length === 0 ? (
|
||||
<p>No hay noticias disponibles en este momento.</p>
|
||||
) : (
|
||||
<>
|
||||
{noticias.map((n) => (
|
||||
<div key={n.id} className="noticia-item">
|
||||
<h3>{n.titulo}</h3>
|
||||
<span className="date">{formatFecha(n.fecha)}</span>
|
||||
<div
|
||||
className="noticia-contenido"
|
||||
dangerouslySetInnerHTML={{ __html: n.contenido }}
|
||||
/>
|
||||
{n.enlace && (
|
||||
<p>
|
||||
Enlace:{' '}
|
||||
<a href={n.enlace} target="_blank" rel="noopener noreferrer">
|
||||
aquí
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
<br />
|
||||
<hr />
|
||||
<br />
|
||||
</div>
|
||||
))}
|
||||
<p className="centered third-brown">
|
||||
<i>* Mostrando las últimas 50 noticias</i>
|
||||
</p>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<main className="mx-auto max-w-4xl px-4 py-10">
|
||||
<div className="grid gap-6 md:grid-cols-3">
|
||||
{/* Estado del servidor */}
|
||||
<aside className="nw-card md:col-span-1">
|
||||
<h2 className="mb-3 text-lg font-semibold text-nw-gold-light">{t('serverStatus')}</h2>
|
||||
{status ? (
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li className="flex justify-between">
|
||||
<span className="text-nw-muted">{t('server')}</span>
|
||||
<span>
|
||||
{status.server_name ?? '—'} <span className="text-nw-muted">({status.expansion})</span>
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span className="text-nw-muted">{t('onlineCharacters')}</span>
|
||||
<span className="text-green-400">{status.online_characters}</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span className="text-nw-muted">{t('login')}</span>
|
||||
<span className={status.status === 'Online' ? 'text-green-400' : 'text-red-400'}>
|
||||
{status.status}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span className="text-nw-muted">{t('address')}</span>
|
||||
<span className="text-nw-gold-light">{status.address ?? '—'}</span>
|
||||
</li>
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-nw-muted">{t('notAvailable')}</p>
|
||||
)}
|
||||
</aside>
|
||||
|
||||
{/* Noticias */}
|
||||
<section className="md:col-span-2">
|
||||
<h2 className="mb-4 text-xl font-semibold text-nw-gold-light">{t('news')}</h2>
|
||||
{noticias.length === 0 ? (
|
||||
<p className="text-nw-muted">{t('noNews')}</p>
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
{noticias.map((n) => (
|
||||
<article key={n.id} className="nw-card">
|
||||
<h3 className="text-lg font-semibold">{n.titulo}</h3>
|
||||
{n.fecha && (
|
||||
<small className="text-nw-muted">{new Date(n.fecha).toLocaleString(locale)}</small>
|
||||
)}
|
||||
<div
|
||||
className="prose prose-invert mt-3 max-w-none text-sm"
|
||||
dangerouslySetInnerHTML={{ __html: n.contenido }}
|
||||
/>
|
||||
{n.enlace && (
|
||||
<p className="mt-3">
|
||||
{t('link')}:{' '}
|
||||
<a href={n.enlace} target="_blank" rel="noopener noreferrer" className="text-sky-400 underline">
|
||||
{t('here')}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user