i18n: traducir toda la app (ES + EN) — páginas y componentes con texto hardcodeado

Se externaliza el texto español hardcodeado de ~55 archivos a next-intl y se añaden
traducciones al inglés, con paridad de claves es/en. Nuevos namespaces: History,
CharService, CharServiceB, Points, Legal, UI, Misc (+ altas en Common/Admin).

- Historiales (PD/PV, transacciones, sanciones, seguridad), servicios de personaje
  (transfer, send-gift, quest, restore-*, change-*, customize, level-up, gold, rename),
  PD/pagos (d-points, trade, promo, transfer-dp, rename-guild, DPointsTabs), páginas
  legales (cookies, privacidad, términos, reembolsos, aviso legal, contacto), layout
  (cabecera, footer, cookies, 2FA, descargas, jugadores, recluta) y páginas varias
  (home, reino, ayuda, addons, 2falogin).
- Textos con markup inline via t.rich; interpolación con ICU.
- Componente <NoteLegend/> para la leyenda NOTA/NOTE compartida.
- payLabel/confirmText de los servicios de pago traducidos.
- Verificado: tsc OK, next build OK, todas las claves t() resuelven en es y en,
  todos los t.rich casan etiquetas, páginas 200 en /es/ y /en/ sin claves crudas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 18:58:54 +00:00
parent e457a43f05
commit 651d8deafc
57 changed files with 3516 additions and 1190 deletions
+3 -2
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { notFound } from 'next/navigation'
import { getRealmName, realmSlug } from '@/lib/realm'
import { RealmInfo } from '@/components/RealmInfo'
@@ -38,10 +38,11 @@ export default async function RealmPage({
}) {
const { locale, realm } = await params
setRequestLocale(locale)
const t = await getTranslations('Misc')
const r = await resolve(realm)
if (!r) notFound()
const heading = r.kind === 'players' ? 'Personajes' : 'Reino'
const heading = r.kind === 'players' ? t('realm.charactersHeading') : t('realm.realmHeading')
return (
<div className="main-page">
+21 -23
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { setRequestLocale, getTranslations } from 'next-intl/server'
import { redirect, Link } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getSanctions, type Sanction } from '@/lib/ban-history'
@@ -21,7 +21,7 @@ function StatusCell({ s }: { s: Sanction }) {
}
/** Caja con la tabla de sanciones (baneos o muteos), o el vacío del diseño. */
function SanctionBox({
async function SanctionBox({
title,
emptyText,
rows,
@@ -32,6 +32,7 @@ function SanctionBox({
rows: Sanction[]
showScope: boolean
}) {
const t = await getTranslations('History')
return (
<div className="box-content">
<div className="title-box-content">
@@ -52,12 +53,12 @@ function SanctionBox({
<table className="max-center-table">
<tbody>
<tr>
{showScope && <th>Alcance</th>}
<th>Motivo</th>
<th>Sancionado por</th>
<th>Fecha</th>
<th>Expira</th>
<th>Estado</th>
{showScope && <th>{t('ban.thScope')}</th>}
<th>{t('ban.thReason')}</th>
<th>{t('ban.thBy')}</th>
<th>{t('ban.thDate')}</th>
<th>{t('ban.thExpires')}</th>
<th>{t('ban.thStatus')}</th>
</tr>
{rows.map((s, i) => (
<tr key={i}>
@@ -76,7 +77,7 @@ function SanctionBox({
<span>{fmt(s.date)}</span>
</td>
<td>
<span>{s.expires ? fmt(s.expires) : 'Permanente'}</span>
<span>{s.expires ? fmt(s.expires) : t('ban.permanent')}</span>
</td>
<td>
<StatusCell s={s} />
@@ -94,6 +95,7 @@ function SanctionBox({
export default async function BanHistoryPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('History')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -102,38 +104,34 @@ export default async function BanHistoryPage({ params }: { params: Promise<{ loc
const { bans, mutes } = await getSanctions(session.accountId!, session.bnetId)
return (
<PageShell title="Historial de sanciones">
<PageShell title={t('ban.pageTitle')}>
<ServiceBox>
<br />
<p>Aquí podrás ver todas las sanciones que ha tenido la cuenta.</p>
<p>{t('ban.intro1')}</p>
<p>
Si tienes una sanción vigente, verás que su estado indica <span className="red-info2">Activo</span>.
{t.rich('ban.intro2', { s: (c) => <span className="red-info2">{c}</span> })}
</p>
<p>Para evitar que tu cuenta sea sancionada, recuerda respetar las normas de nuestro servidor.</p>
<p>{t('ban.intro3')}</p>
<br />
<div className="restore-item-table">
<p>
<i className="fas fa-exclamation-triangle"></i> <span>Importante:</span>
<i className="fas fa-exclamation-triangle"></i> <span>{t('ban.important')}</span>
</p>
<p>
Si tu cuenta aparece como sancionada o cerrada al intentar ingresar al reino, pero tu historial no tiene
ninguna sanción activa, significa que has tenido un bloqueo automático temporal por demasiados intentos de
inicio de sesión fallidos.
{t('ban.autoBlock1')}
</p>
<p>
Espera al menos 10 minutos para volver a intentar y asegúrate de usar nombre de cuenta y contraseña
correctas.
{t('ban.autoBlock2')}
</p>
</div>
<br />
<p>
Para realizar un reclamo de sanción, por favor ingresa en nuestro <Link href="/forum">foro</Link> y visita la
sección Reclamaciones.
{t.rich('ban.claim', { link: (c) => <Link href="/forum">{c}</Link> })}
</p>
</ServiceBox>
<SanctionBox title="Historial de baneos" emptyText="No hay baneos" rows={bans} showScope />
<SanctionBox title="Historial de muteos" emptyText="No hay muteos" rows={mutes} showScope={false} />
<SanctionBox title={t('ban.bansTitle')} emptyText={t('ban.noBans')} rows={bans} showScope />
<SanctionBox title={t('ban.mutesTitle')} emptyText={t('ban.noMutes')} rows={mutes} showScope={false} />
</PageShell>
)
}
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -34,34 +34,33 @@ export default async function ChangeFactionCharacterPage({ params }: { params: P
const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getChangeFactionPrice()])
const t = await getTranslations('CharServiceB.changeFaction')
return (
<PageShell title="Cambiar facción del personaje">
<ServiceBox>
<br />
<p>
La herramienta <span>Cambiar facción del personaje</span> te permite cambiar de la Alianza a la Horda y
viceversa.
</p>
<p>Si también deseas cambiar el nombre del personaje, debes usar la herramienta Renombrar personaje.</p>
<p>{t.rich('intro', { s: (c) => <span>{c}</span> })}</p>
<p>{t('alsoRename')}</p>
<br />
<p>Al hacer un cambio de Facción:</p>
<p>- Los items, hechizos, títulos, reputaciones, monturas y logros se cambiarán a la nueva facción</p>
<p>- Las misiones activas en el Registro de misiones serán abandonadas</p>
<p>- Los teams de arena serán borrados</p>
<p>- Los amigos en la Lista de amigos se borrarán</p>
<p>{t('onChange')}</p>
<p>{t('change1')}</p>
<p>{t('change2')}</p>
<p>{t('change3')}</p>
<p>{t('change4')}</p>
<br />
<p><span className="red-info2">Restricciones del personaje:</span></p>
<p>- No debe tener subastas activas</p>
<p>- No debe tener correos en el buzón</p>
<p>- No debe ser capitán de un team de arenas</p>
<p>- No debe ser miembro de una hermandad</p>
<p>- No debe tener demasiado oro</p>
<p><span className="red-info2">{t('restrictionsTitle')}</span></p>
<p>{t('restriction1')}</p>
<p>{t('restriction2')}</p>
<p>{t('restriction3')}</p>
<p>{t('restriction4')}</p>
<p>{t('restriction5')}</p>
<br />
<table className="max-gold-table">
<tbody>
<tr>
<th>Nivel</th>
<th>Max. Oro</th>
<th>{t('tableLevel')}</th>
<th>{t('tableMaxGold')}</th>
</tr>
<tr><td>10-30</td><GoldCell amount={300} /></tr>
<tr><td>31-50</td><GoldCell amount={1000} /></tr>
@@ -70,36 +69,30 @@ export default async function ChangeFactionCharacterPage({ params }: { params: P
</tbody>
</table>
<br />
<p>
Al usar el botón CAMBIAR FACCIÓN del personaje que hayas escogido, se enviará una petición de cambio de
facción de dicho personaje.
</p>
<p>Cuando ingreses a la lista de personajes de tu cuenta, verás un ícono a la izquierda de tu personaje escogido.</p>
<p>Al darle click a dicho ícono ingresarás al menú de edición de personaje que te permitirá pasar a la facción contraria.</p>
<p>{t('instructions')}</p>
<p>{t('iconInfo')}</p>
<p>{t('iconClick')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('noteLegend')}</legend>
<div className="separate2">
<p>
Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez
realizada.
</p>
<p>{t('noteConfirm')}</p>
</div>
</fieldset>
<div className="centered">
<br />
<br />
<p>Requiere <span>{price}</span> <span className="yellow-info"></span> (SumUp)</p>
<p>{t.rich('requires', { price, s: (c) => <span>{c}</span>, y: (c) => <span className="yellow-info">{c}</span> })}</p>
</div>
<PaidServiceForm
characters={chars.map((c) => ({ name: c.name, classCss: c.classCss }))}
checkoutEndpoint="/api/character/change-faction/checkout"
payLabel="CAMBIAR FACCIÓN"
payLabel={t('payLabel')}
buttonClass="change-faction-button"
provider="sumup"
confirmText={`¿Estás seguro de cambiar de facción al personaje seleccionado por ${price} € (SumUp)?`}
confirmText={t('confirm', { price })}
/>
</ServiceBox>
</PageShell>
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -22,53 +22,43 @@ export default async function ChangeRaceCharacterPage({ params }: { params: Prom
const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getChangeRacePrice()])
const t = await getTranslations('CharServiceB.changeRace')
return (
<PageShell title="Cambiar raza del personaje">
<ServiceBox>
<br />
<p className="red-info2 h2">
<i className="fas fa-exclamation-triangle"></i> ESTE CAMBIO NO PERMITE CAMBIAR A LA FACCIÓN CONTRARIA{' '}
<i className="fas fa-exclamation-triangle"></i> {t('warning')}{' '}
<i className="fas fa-exclamation-triangle"></i>
</p>
<p>
La herramienta <span>Cambiar raza del personaje</span> te permite cambiar la raza del personaje entre otras de
la MISMA facción.
</p>
<p>Si también deseas cambiar el nombre del personaje, debes usar la herramienta Renombrar personaje.</p>
<p>{t.rich('intro', { s: (c) => <span>{c}</span> })}</p>
<p>{t('alsoRename')}</p>
<br />
<p>
Al usar el botón CAMBIAR RAZA del personaje que hayas escogido, se enviará una petición de cambio de raza de
dicho personaje.
</p>
<p>Cuando ingreses a la lista de personajes de tu cuenta, verás un ícono a la izquierda de tu personaje escogido.</p>
<p>
Al darle click a dicho ícono ingresarás al menú de edición de personaje que te permitirá escoger entre las
razas de la misma facción.
</p>
<p>{t('instructions')}</p>
<p>{t('iconInfo')}</p>
<p>{t('iconClick')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('noteLegend')}</legend>
<div className="separate2">
<p>
Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez
realizada.
</p>
<p>{t('noteConfirm')}</p>
</div>
</fieldset>
<div className="centered">
<br />
<br />
<p>Requiere <span>{price}</span> <span className="yellow-info"></span> (SumUp)</p>
<p>{t.rich('requires', { price, s: (c) => <span>{c}</span>, y: (c) => <span className="yellow-info">{c}</span> })}</p>
</div>
<PaidServiceForm
characters={chars.map((c) => ({ name: c.name, classCss: c.classCss }))}
checkoutEndpoint="/api/character/change-race/checkout"
payLabel="CAMBIAR RAZA"
payLabel={t('payLabel')}
buttonClass="change-race-button"
provider="sumup"
confirmText={`¿Estás seguro de cambiar de raza al personaje seleccionado por ${price} € (SumUp)?`}
confirmText={t('confirm', { price })}
/>
</ServiceBox>
</PageShell>
+12 -10
View File
@@ -1,31 +1,33 @@
import type { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
export const metadata: Metadata = {
title: 'Contáctanos',
}
export default function ContactUsPage() {
export default async function ContactUsPage() {
const t = await getTranslations('Legal')
return (
<div className="main-page">
<div className="middle-content">
<div className="body-content">
<div className="title-content">
<h1>Contáctanos</h1>
<h1>{t('contact.title')}</h1>
</div>
<div className="box-content">
<div className="body-box-content">
<p>¡Gracias por tu interés en contactar con nosotros!</p>
<p>Si tienes alguna pregunta, consulta o comentario, estaremos encantados de ayudarte. Por favor, envía un correo electrónico a la siguiente dirección:</p>
<p>{t('contact.p1')}</p>
<p>{t('contact.p2')}</p>
<p className="second-brown">contacto@ultimowow.com</p>
<br />
<p>Error 404 es la empresa que representa el servicio de UltimoWoW.</p>
<p>Nos esforzamos por responder a todos los mensajes en un plazo de 48 horas hábiles.</p>
<p>Asegúrate de incluir la información relevante en tu correo para que podamos brindarte una respuesta precisa y oportuna.</p>
<p>{t('contact.p3')}</p>
<p>{t('contact.p4')}</p>
<p>{t('contact.p5')}</p>
<br />
<p>En Error 404, valoramos tus comentarios y opiniones. Tu satisfacción es nuestra prioridad y nos esforzamos por brindarte la mejor experiencia posible en UltimoWoW.</p>
<p>¡Esperamos tener noticias tuyas pronto!</p>
<p>{t('contact.p6')}</p>
<p>{t('contact.p7')}</p>
<br />
<p>Atentamente, el equipo de Error 404.</p>
<p>{t('contact.p8')}</p>
</div>
</div>
</div>
+84 -82
View File
@@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
import { Link } from '@/i18n/navigation'
import { CookieConsentButtons, ConsentStatus } from '@/components/CookieConsent'
@@ -6,172 +7,173 @@ export const metadata: Metadata = {
title: 'Declaración de Cookies',
}
export default function CookiesPage() {
export default async function CookiesPage() {
const t = await getTranslations('Legal')
return (
<div className="main-page">
<div className="middle-content">
<div className="body-content">
<div className="title-content">
<h1>Declaración de Cookies</h1>
<h1>{t('cookies.title')}</h1>
</div>
<div className="box-content">
<div className="body-box-content justified">
<p>Esta página web usa cookies. Las cookies de este sitio web se usan para personalizar el contenido y los anuncios, ofrecer funciones de redes sociales y analizar el tráfico. Además, compartimos información sobre el uso que haga del sitio web con nuestros partners de redes sociales, publicidad y análisis web, quienes pueden combinarla con otra información que les haya proporcionado o que hayan recopilado a partir del uso que haya hecho de sus servicios.</p>
<p>{t('cookies.p1')}</p>
<br />
<p>Las cookies son pequeños archivos de texto que las páginas web pueden utilizar para hacer más eficiente la experiencia del usuario.</p>
<p>La ley afirma que podemos almacenar cookies en tu dispositivo si son estrictamente necesarias para el funcionamiento de esta página. Para todos los demás tipos de cookies necesitamos tu permiso.</p>
<p>{t('cookies.p2')}</p>
<p>{t('cookies.p3')}</p>
<br />
<p>Esta página utiliza tipos diferentes de cookies. Algunas cookies son colocadas por servicios de terceros que aparecen en nuestras páginas.</p>
<p>En cualquier momento puedes cambiar o retirar tu consentimiento desde esta Declaración de cookies en nuestro sitio web.</p>
<p>{t('cookies.p4')}</p>
<p>{t('cookies.p5')}</p>
<br />
<p>Obten más información sobre quiénes somos, cómo puedes contactarnos y cómo procesamos los datos personales en nuestra <Link href="/privacy-policy">Política de Privacidad</Link>.</p>
<p>{t.rich('cookies.p6', { link: (c) => <Link href="/privacy-policy">{c}</Link> })}</p>
<br />
<hr />
<br />
<span>Tipos de cookies que utilizamos</span>
<span>{t('cookies.h1cat')}</span>
<br />
<span className="second-brown">Cookies Necesarias</span>
<p>Las cookies necesarias ayudan a hacer una página web utilizable activando funciones básicas como la navegación en la página y el acceso a áreas seguras de la página web. La página web no puede funcionar adecuadamente sin estas cookies.</p>
<span className="second-brown">{t('cookies.necessaryTitle')}</span>
<p>{t('cookies.necessaryDesc')}</p>
<table className="uw-cookie-table">
<thead>
<tr>
<th>Cookie</th>
<th>Proveedor</th>
<th>Propósito</th>
<th>Duración</th>
<th>{t('cookies.thCookie')}</th>
<th>{t('cookies.thProvider')}</th>
<th>{t('cookies.thPurpose')}</th>
<th>{t('cookies.thDuration')}</th>
</tr>
</thead>
<tbody>
<tr><td>PHPSESSID</td><td>UltimoWoW</td><td>Mantiene el estado de la sesión del usuario entre páginas. Presente en ultimowow.com y wotlk.ultimowow.com.</td><td>Sesión</td></tr>
<tr><td>uw_cookie_consent</td><td>UltimoWoW</td><td>Almacena tus preferencias de consentimiento de cookies. Se comparte entre ultimowow.com y wotlk.ultimowow.com.</td><td>1 año</td></tr>
<tr><td>cf_clearance</td><td>Cloudflare</td><td>Identifica tráfico legítimo y protege contra bots.</td><td>1 año</td></tr>
<tr><td>__cf_bm</td><td>Cloudflare</td><td>Bot management y protección contra ataques DDoS.</td><td>30 minutos</td></tr>
<tr><td>_cfuvid</td><td>Cloudflare</td><td>Identificador de sesión para gestión de tráfico.</td><td>Sesión</td></tr>
<tr><td>CF_VERIFIED_DEVICE_*</td><td>Cloudflare</td><td>Verificación de dispositivo para seguridad.</td><td>30 días</td></tr>
<tr><td>_cf_logged_in</td><td>Cloudflare</td><td>Indica estado de autenticación en servicios Cloudflare.</td><td>Sesión</td></tr>
<tr><td>cf_chl_*</td><td>Cloudflare Turnstile</td><td>Cookies del sistema de verificación captcha Turnstile.</td><td>Variable</td></tr>
<tr><td>cf.turnstile.u</td><td>Cloudflare Turnstile</td><td>Distingue entre humanos y bots en el sistema de verificación.</td><td>Persistente</td></tr>
<tr><td>zaraz-consent</td><td>Cloudflare Zaraz</td><td>Gestión de consentimiento del sistema Zaraz.</td><td>1 año</td></tr>
<tr><td>cfz_*</td><td>Cloudflare Zaraz</td><td>Cookies técnicas del sistema de gestión de tags Zaraz.</td><td>Variable</td></tr>
<tr><td>PHPSESSID</td><td>UltimoWoW</td><td>{t('cookies.nPurpose1')}</td><td>{t('cookies.durSession')}</td></tr>
<tr><td>uw_cookie_consent</td><td>UltimoWoW</td><td>{t('cookies.nPurpose2')}</td><td>{t('cookies.dur1Year')}</td></tr>
<tr><td>cf_clearance</td><td>Cloudflare</td><td>{t('cookies.nPurpose3')}</td><td>{t('cookies.dur1Year')}</td></tr>
<tr><td>__cf_bm</td><td>Cloudflare</td><td>{t('cookies.nPurpose4')}</td><td>{t('cookies.dur30Min')}</td></tr>
<tr><td>_cfuvid</td><td>Cloudflare</td><td>{t('cookies.nPurpose5')}</td><td>{t('cookies.durSession')}</td></tr>
<tr><td>CF_VERIFIED_DEVICE_*</td><td>Cloudflare</td><td>{t('cookies.nPurpose6')}</td><td>{t('cookies.dur30Days')}</td></tr>
<tr><td>_cf_logged_in</td><td>Cloudflare</td><td>{t('cookies.nPurpose7')}</td><td>{t('cookies.durSession')}</td></tr>
<tr><td>cf_chl_*</td><td>Cloudflare Turnstile</td><td>{t('cookies.nPurpose8')}</td><td>{t('cookies.durVariable')}</td></tr>
<tr><td>cf.turnstile.u</td><td>Cloudflare Turnstile</td><td>{t('cookies.nPurpose9')}</td><td>{t('cookies.durPersistent')}</td></tr>
<tr><td>zaraz-consent</td><td>Cloudflare Zaraz</td><td>{t('cookies.nPurpose10')}</td><td>{t('cookies.dur1Year')}</td></tr>
<tr><td>cfz_*</td><td>Cloudflare Zaraz</td><td>{t('cookies.nPurpose11')}</td><td>{t('cookies.durVariable')}</td></tr>
</tbody>
</table>
<p className="third-brown">Nota sobre Cloudflare: Utilizamos Cloudflare como proveedor de seguridad y rendimiento. Cloudflare puede establecer cookies adicionales de forma dinámica para proteger el sitio contra amenazas, gestionar el tráfico y optimizar el rendimiento. Estas cookies son estrictamente necesarias para el funcionamiento seguro del sitio. Para más información, consulta la <a href="https://www.cloudflare.com/cookie-policy/" target="_blank" rel="noopener">Política de Cookies de Cloudflare</a>.</p>
<p className="third-brown">{t.rich('cookies.cfNote', { link: (c) => <a href="https://www.cloudflare.com/cookie-policy/" target="_blank" rel="noopener">{c}</a> })}</p>
<br />
<span className="second-brown">Cookies de Preferencias</span>
<p>Las cookies de preferencias permiten a la página web recordar información que cambia la forma en que la página se comporta o el aspecto que tiene, como su idioma preferido o la región en la que usted se encuentra.</p>
<span className="second-brown">{t('cookies.prefTitle')}</span>
<p>{t('cookies.prefDesc')}</p>
<table className="uw-cookie-table">
<thead>
<tr>
<th>Cookie</th>
<th>Proveedor</th>
<th>Propósito</th>
<th>Duración</th>
<th>{t('cookies.thCookie')}</th>
<th>{t('cookies.thProvider')}</th>
<th>{t('cookies.thPurpose')}</th>
<th>{t('cookies.thDuration')}</th>
</tr>
</thead>
<tbody>
<tr><td>comments_sort</td><td>wotlk.ultimowow.com</td><td>Guarda tu preferencia de ordenación de comentarios.</td><td>1 año</td></tr>
<tr><td>wwcomments_sort</td><td>wotlk.ultimowow.com</td><td>Guarda tu preferencia de ordenación de comentarios.</td><td>1 año</td></tr>
<tr><td>temp_default_3dmodel</td><td>wotlk.ultimowow.com</td><td>Guarda la configuración del visor 3D de modelos.</td><td>1 año</td></tr>
<tr><td>comments_sort</td><td>wotlk.ultimowow.com</td><td>{t('cookies.prefPurpose1')}</td><td>{t('cookies.dur1Year')}</td></tr>
<tr><td>wwcomments_sort</td><td>wotlk.ultimowow.com</td><td>{t('cookies.prefPurpose2')}</td><td>{t('cookies.dur1Year')}</td></tr>
<tr><td>temp_default_3dmodel</td><td>wotlk.ultimowow.com</td><td>{t('cookies.prefPurpose3')}</td><td>{t('cookies.dur1Year')}</td></tr>
</tbody>
</table>
<span className="second-brown">Cookies Analíticas</span>
<p>Las cookies analíticas nos permiten contar las visitas y fuentes de tráfico para poder medir y mejorar el rendimiento de nuestro sitio. Nos ayudan a saber qué páginas son las más y menos populares, y a ver cómo se mueven los visitantes por el sitio. Toda la información que recogen estas cookies es agregada y, por lo tanto, anónima.</p>
<span className="second-brown">{t('cookies.analyticsTitle')}</span>
<p>{t('cookies.analyticsDesc')}</p>
<table className="uw-cookie-table">
<thead>
<tr>
<th>Cookie</th>
<th>Proveedor</th>
<th>Propósito</th>
<th>Duración</th>
<th>{t('cookies.thCookie')}</th>
<th>{t('cookies.thProvider')}</th>
<th>{t('cookies.thPurpose')}</th>
<th>{t('cookies.thDuration')}</th>
</tr>
</thead>
<tbody>
<tr><td>_ga</td><td>Google Analytics</td><td>Distingue usuarios únicos asignando un número aleatorio.</td><td>2 años</td></tr>
<tr><td>_ga_*</td><td>Google Analytics</td><td>Mantiene el estado de la sesión.</td><td>2 años</td></tr>
<tr><td>_gid</td><td>Google Analytics</td><td>Distingue usuarios para análisis estadístico.</td><td>24 horas</td></tr>
<tr><td>_gat</td><td>Google Analytics</td><td>Limita la velocidad de solicitudes.</td><td>1 minuto</td></tr>
<tr><td>sparrow_id</td><td>Cloudflare</td><td>Identificador para análisis de rendimiento web.</td><td>1 año</td></tr>
<tr><td>_ga</td><td>Google Analytics</td><td>{t('cookies.aPurpose1')}</td><td>{t('cookies.dur2Years')}</td></tr>
<tr><td>_ga_*</td><td>Google Analytics</td><td>{t('cookies.aPurpose2')}</td><td>{t('cookies.dur2Years')}</td></tr>
<tr><td>_gid</td><td>Google Analytics</td><td>{t('cookies.aPurpose3')}</td><td>{t('cookies.dur24Hours')}</td></tr>
<tr><td>_gat</td><td>Google Analytics</td><td>{t('cookies.aPurpose4')}</td><td>{t('cookies.dur1Minute')}</td></tr>
<tr><td>sparrow_id</td><td>Cloudflare</td><td>{t('cookies.aPurpose5')}</td><td>{t('cookies.dur1Year')}</td></tr>
</tbody>
</table>
<span className="second-brown">Cookies de Marketing</span>
<p>Las cookies de marketing se utilizan para rastrear a los visitantes en las páginas web. La intención es mostrar anuncios relevantes y atractivos para el usuario individual, y por lo tanto, más valiosos para los editores y terceros anunciantes.</p>
<span className="second-brown">{t('cookies.marketingTitle')}</span>
<p>{t('cookies.marketingDesc')}</p>
<table className="uw-cookie-table">
<thead>
<tr>
<th>Cookie</th>
<th>Proveedor</th>
<th>Propósito</th>
<th>Duración</th>
<th>{t('cookies.thCookie')}</th>
<th>{t('cookies.thProvider')}</th>
<th>{t('cookies.thPurpose')}</th>
<th>{t('cookies.thDuration')}</th>
</tr>
</thead>
<tbody>
<tr><td>_gcl_*</td><td>Google</td><td>Conversiones de Google Ads.</td><td>90 días</td></tr>
<tr><td>IDE</td><td>Google DoubleClick</td><td>Publicidad personalizada.</td><td>1 año</td></tr>
<tr><td>_fbp</td><td>Facebook (Meta)</td><td>Identifica navegadores para proporcionar servicios de publicidad y análisis.</td><td>3 meses</td></tr>
<tr><td>_fbc</td><td>Facebook (Meta)</td><td>Almacena el último clic en anuncios de Facebook.</td><td>3 meses</td></tr>
<tr><td>fr</td><td>Facebook (Meta)</td><td>Habilita la entrega y medición de anuncios.</td><td>3 meses</td></tr>
<tr><td>AMCV_*</td><td>Adobe Experience Cloud</td><td>Identificación de visitantes para servicios de Adobe.</td><td>2 años</td></tr>
<tr><td>_q_state_*</td><td>Varios</td><td>Gestión de estado para servicios de publicidad.</td><td>Variable</td></tr>
<tr><td>_gcl_*</td><td>Google</td><td>{t('cookies.mPurpose1')}</td><td>{t('cookies.dur90Days')}</td></tr>
<tr><td>IDE</td><td>Google DoubleClick</td><td>{t('cookies.mPurpose2')}</td><td>{t('cookies.dur1Year')}</td></tr>
<tr><td>_fbp</td><td>Facebook (Meta)</td><td>{t('cookies.mPurpose3')}</td><td>{t('cookies.dur3Months')}</td></tr>
<tr><td>_fbc</td><td>Facebook (Meta)</td><td>{t('cookies.mPurpose4')}</td><td>{t('cookies.dur3Months')}</td></tr>
<tr><td>fr</td><td>Facebook (Meta)</td><td>{t('cookies.mPurpose5')}</td><td>{t('cookies.dur3Months')}</td></tr>
<tr><td>AMCV_*</td><td>Adobe Experience Cloud</td><td>{t('cookies.mPurpose6')}</td><td>{t('cookies.dur2Years')}</td></tr>
<tr><td>_q_state_*</td><td>{t('cookies.providerVarious')}</td><td>{t('cookies.mPurpose7')}</td><td>{t('cookies.durVariable')}</td></tr>
</tbody>
</table>
<p className="third-brown">Nota sobre servicios de terceros: Utilizamos Cloudflare Zaraz como sistema de gestión de tags. Los servicios de marketing (Google Ads, Facebook, Adobe, etc.) pueden establecer cookies adicionales cuando aceptas cookies de marketing. Para más información sobre cada servicio, consulta sus respectivas políticas de privacidad.</p>
<p className="third-brown">{t('cookies.thirdPartyNote')}</p>
<br />
<span className="second-brown">Cookies de Contenido Embebido</span>
<p>Algunas páginas de nuestro sitio incluyen contenido embebido de terceros (vídeos de YouTube, widgets de Discord, publicaciones de redes sociales, etc.). Estos servicios pueden establecer sus propias cookies cuando visualizas dicho contenido.</p>
<span className="second-brown">{t('cookies.embeddedTitle')}</span>
<p>{t('cookies.embeddedDesc')}</p>
<table className="uw-cookie-table">
<thead>
<tr>
<th>Proveedor</th>
<th>Tipo de contenido</th>
<th>Más información</th>
<th>{t('cookies.thProvider')}</th>
<th>{t('cookies.thContentType')}</th>
<th>{t('cookies.thMoreInfo')}</th>
</tr>
</thead>
<tbody>
<tr><td>YouTube (Google)</td><td>Vídeos embebidos</td><td><a href="https://policies.google.com/privacy" target="_blank" rel="noopener">Política de Privacidad</a></td></tr>
<tr><td>Discord</td><td>Widgets de servidor</td><td><a href="https://discord.com/privacy" target="_blank" rel="noopener">Política de Privacidad</a></td></tr>
<tr><td>Twitch</td><td>Streams embebidos</td><td><a href="https://www.twitch.tv/p/legal/privacy-notice/" target="_blank" rel="noopener">Política de Privacidad</a></td></tr>
<tr><td>Facebook (Meta)</td><td>Vídeos y publicaciones</td><td><a href="https://www.facebook.com/privacy/policy/" target="_blank" rel="noopener">Política de Privacidad</a></td></tr>
<tr><td>YouTube (Google)</td><td>{t('cookies.embType1')}</td><td><a href="https://policies.google.com/privacy" target="_blank" rel="noopener">{t('cookies.privacyPolicyLink')}</a></td></tr>
<tr><td>Discord</td><td>{t('cookies.embType2')}</td><td><a href="https://discord.com/privacy" target="_blank" rel="noopener">{t('cookies.privacyPolicyLink')}</a></td></tr>
<tr><td>Twitch</td><td>{t('cookies.embType3')}</td><td><a href="https://www.twitch.tv/p/legal/privacy-notice/" target="_blank" rel="noopener">{t('cookies.privacyPolicyLink')}</a></td></tr>
<tr><td>Facebook (Meta)</td><td>{t('cookies.embType4')}</td><td><a href="https://www.facebook.com/privacy/policy/" target="_blank" rel="noopener">{t('cookies.privacyPolicyLink')}</a></td></tr>
</tbody>
</table>
<p className="third-brown">Utilizamos youtube-nocookie.com cuando es posible para minimizar el seguimiento. Sin embargo, los servicios de terceros pueden establecer cookies como: YSC, VISITOR_INFO1_LIVE, yt-remote-*, __Secure-*, y otras relacionadas con la reproducción de contenido.</p>
<p className="third-brown">{t('cookies.embeddedNote')}</p>
<br />
<hr />
<br />
<span>Cómo eliminar cookies de tu navegador</span>
<p>Además de utilizar nuestro panel de configuración, puedes eliminar las cookies directamente desde tu navegador:</p>
<span>{t('cookies.h2cat')}</span>
<p>{t('cookies.deleteP')}</p>
<ul>
<li>Chrome: Configuración Privacidad y seguridad Cookies y otros datos de sitios</li>
<li>Firefox: Opciones Privacidad &amp; Seguridad Cookies y datos del sitio</li>
<li>Safari: Preferencias Privacidad Gestionar datos de sitios web</li>
<li>Edge: Configuración Cookies y permisos del sitio Cookies</li>
<li>{t('cookies.deleteLi1')}</li>
<li>{t('cookies.deleteLi2')}</li>
<li>{t('cookies.deleteLi3')}</li>
<li>{t('cookies.deleteLi4')}</li>
</ul>
<br />
<hr />
<br />
<span>Tu estado de consentimiento</span>
<p>Puedes cambiar o retirar tu consentimiento en cualquier momento. Al contactarnos respecto a su consentimiento, por favor, indique la fecha de su consentimiento.</p>
<span>{t('cookies.h3cat')}</span>
<p>{t('cookies.consentP')}</p>
<ConsentStatus />
<CookieConsentButtons />
<br />
<hr />
<br />
<span>Más información</span>
<p>Si tienes alguna pregunta sobre nuestra política de cookies, puedes contactarnos a través de nuestra página de <Link href="/contact-us">Contacto</Link>.</p>
<span>{t('cookies.h4cat')}</span>
<p>{t.rich('cookies.moreInfoP', { link: (c) => <Link href="/contact-us">{c}</Link> })}</p>
<br />
<p className="grey-info2">Última actualización: Marzo 2026</p>
<p className="grey-info2">{t('cookies.updated')}</p>
</div>
</div>
</div>
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -22,53 +22,44 @@ export default async function CustomizeCharacterPage({ params }: { params: Promi
const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getCustomizePrice()])
const t = await getTranslations('CharServiceB.customize')
return (
<PageShell title="Personalizar personaje">
<ServiceBox>
<br />
<p>
La herramienta <span>Personalizar personaje</span> te permite cambiar los siguientes aspectos un personaje:
</p>
<p>- Color de la piel</p>
<p>- Rostro</p>
<p>- Pelo</p>
<p>- Sexo</p>
<p>- Características correspondientes a cada raza como cuernos, pendientes, marcas, vello facial</p>
<p>Si también deseas cambiar el nombre del personaje, debes usar la herramienta Renombrar personaje.</p>
<p>{t.rich('intro', { s: (c) => <span>{c}</span> })}</p>
<p>{t('aspect1')}</p>
<p>{t('aspect2')}</p>
<p>{t('aspect3')}</p>
<p>{t('aspect4')}</p>
<p>{t('aspect5')}</p>
<p>{t('alsoRename')}</p>
<br />
<p>
Al usar el botón PERSONALIZAR del personaje que hayas escogido, se enviará una petición de personalización de
dicho personaje.
</p>
<p>Cuando ingreses a la lista de personajes de tu cuenta, verás un ícono a la izquierda de tu personaje escogido.</p>
<p>
Al darle click a dicho ícono ingresarás al menú de edición de personaje que te permitirá escoger diferentes
características.
</p>
<p>{t('instructions')}</p>
<p>{t('iconInfo')}</p>
<p>{t('iconClick')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('noteLegend')}</legend>
<div className="separate2">
<p>
Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez
realizada.
</p>
<p>{t('noteConfirm')}</p>
</div>
</fieldset>
<div className="centered">
<br />
<br />
<p>Requiere <span>{price}</span> <span className="yellow-info"></span> (SumUp)</p>
<p>{t.rich('requires', { price, s: (c) => <span>{c}</span>, y: (c) => <span className="yellow-info">{c}</span> })}</p>
</div>
<PaidServiceForm
characters={chars.map((c) => ({ name: c.name, classCss: c.classCss }))}
checkoutEndpoint="/api/character/customize/checkout"
payLabel="PERSONALIZAR"
payLabel={t('payLabel')}
buttonClass="customize-button"
provider="sumup"
confirmText={`¿Estás seguro de personalizar al personaje seleccionado por ${price} € (SumUp)?`}
confirmText={t('confirm', { price })}
/>
</ServiceBox>
</PageShell>
@@ -1,4 +1,4 @@
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { Link } from '@/i18n/navigation'
import { fulfillCheckoutSession } from '@/lib/fulfill'
import { isSessionPaid } from '@/lib/stripe'
@@ -16,6 +16,7 @@ export default async function DPointsSuccessPage({
}) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Points')
const { provider, session_id, ref } = await searchParams
// Entrega compartida con el webhook/return (idempotente vía reclamo atómico).
@@ -32,20 +33,20 @@ export default async function DPointsSuccessPage({
}
return (
<PageShell title="Adquirir PD">
<PageShell title={t('dpointsSuccess.title')}>
<div className="box-content">
<div className="body-box-content centered">
{status === 'ok' ? (
<span className="ok-form-response">¡Pago completado! Tus PD ya fueron acreditados a tu cuenta.</span>
<span className="ok-form-response">{t('dpointsSuccess.paidOk')}</span>
) : status === 'already' ? (
<span className="ok-form-response">Este pago ya había sido procesado. Tus PD están acreditados.</span>
<span className="ok-form-response">{t('dpointsSuccess.alreadyPaid')}</span>
) : (
<span className="red-form-response">No pudimos confirmar tu pago. Si se realizó el cargo, contacta con soporte.</span>
<span className="red-form-response">{t('dpointsSuccess.error')}</span>
)}
<br />
<br />
<p>
<Link href="/my-account"> Regresar a mi cuenta</Link>
<Link href="/my-account">{t('dpointsSuccess.backToAccount')}</Link>
</p>
</div>
</div>
+3 -2
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getRealmName } from '@/lib/realm'
@@ -14,6 +14,7 @@ export const metadata: Metadata = { title: 'Adquirir PD' }
export default async function DPointsPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Points')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -27,7 +28,7 @@ export default async function DPointsPage({ params }: { params: Promise<{ locale
const currency = (process.env.DP_CURRENCY || 'eur').toLowerCase()
return (
<PageShell title="Adquirir PD">
<PageShell title={t('dpoints.title')}>
<DPointsTabs realm={realm} currency={currency} />
</PageShell>
)
+23 -21
View File
@@ -1,40 +1,42 @@
import type { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
export const metadata: Metadata = { title: 'Descarga de addons' }
// Lista de addons (edítala con los tuyos). name/desc/url.
const ADDONS: { name: string; desc: string; url: string }[] = [
{ name: 'Deadly Boss Mods (DBM)', desc: 'Avisos y temporizadores de habilidades de jefes en mazmorras y bandas.', url: 'https://www.curseforge.com/wow/addons/deadly-boss-mods' },
{ name: 'Details! Damage Meter', desc: 'Medidor de daño, curación y amenaza con gráficas detalladas.', url: 'https://www.curseforge.com/wow/addons/details' },
{ name: 'AtlasLoot', desc: 'Consulta el botín de jefes de mazmorras y bandas dentro del juego.', url: 'https://www.curseforge.com/wow/addons/atlasloot-enhanced' },
{ name: 'Questie', desc: 'Muestra objetivos y ubicaciones de misiones en el mapa y el minimapa.', url: 'https://www.curseforge.com/wow/addons/questie' },
{ name: 'Bagnon', desc: 'Unifica todas tus bolsas y el banco en una sola ventana con búsqueda.', url: 'https://www.curseforge.com/wow/addons/bagnon' },
{ name: 'Bartender4', desc: 'Personaliza por completo tus barras de acción.', url: 'https://www.curseforge.com/wow/addons/bartender4' },
{ name: 'Auctioneer', desc: 'Herramientas avanzadas para la casa de subastas.', url: 'https://www.curseforge.com/wow/addons/auctioneer' },
{ name: 'Pawn', desc: 'Compara equipo y te indica qué objeto es una mejora para tu personaje.', url: 'https://www.curseforge.com/wow/addons/pawn' },
// Lista de addons (edítala con los tuyos). name/descKey/url.
const ADDONS: { name: string; descKey: string; url: string }[] = [
{ name: 'Deadly Boss Mods (DBM)', descKey: 'descDbm', url: 'https://www.curseforge.com/wow/addons/deadly-boss-mods' },
{ name: 'Details! Damage Meter', descKey: 'descDetails', url: 'https://www.curseforge.com/wow/addons/details' },
{ name: 'AtlasLoot', descKey: 'descAtlasLoot', url: 'https://www.curseforge.com/wow/addons/atlasloot-enhanced' },
{ name: 'Questie', descKey: 'descQuestie', url: 'https://www.curseforge.com/wow/addons/questie' },
{ name: 'Bagnon', descKey: 'descBagnon', url: 'https://www.curseforge.com/wow/addons/bagnon' },
{ name: 'Bartender4', descKey: 'descBartender4', url: 'https://www.curseforge.com/wow/addons/bartender4' },
{ name: 'Auctioneer', descKey: 'descAuctioneer', url: 'https://www.curseforge.com/wow/addons/auctioneer' },
{ name: 'Pawn', descKey: 'descPawn', url: 'https://www.curseforge.com/wow/addons/pawn' },
]
export default function DownloadAddonsPage() {
export default async function DownloadAddonsPage() {
const t = await getTranslations('Misc')
return (
<div className="main-page">
<div className="middle-content">
<div className="body-content">
<div className="title-content">
<h1>Descarga de addons</h1>
<h1>{t('downloadAddons.title')}</h1>
</div>
<div className="box-content">
<div className="title-box-content">
<h2>Addons recomendados</h2>
<h2>{t('downloadAddons.recommended')}</h2>
</div>
<div className="body-box-content justified">
<p>Los addons son complementos opcionales que mejoran y personalizan tu experiencia de juego.</p>
<p>Descarga los que quieras desde su página oficial (elige la versión compatible con WotLK 3.4.3).</p>
<p>{t('downloadAddons.intro1')}</p>
<p>{t('downloadAddons.intro2')}</p>
<br />
<p><span>Cómo instalarlos</span></p>
<p>1) Descarga el addon y descomprime el archivo .zip.</p>
<p>2) Copia la carpeta resultante dentro de <span>Interface/AddOns</span> (en la carpeta de tu cliente).</p>
<p>3) Inicia el juego y, en la pantalla de selección de personaje, pulsa <span>Complementos</span> para activarlos.</p>
<p><span>{t('downloadAddons.howToInstall')}</span></p>
<p>{t('downloadAddons.step1')}</p>
<p>{t.rich('downloadAddons.step2', { s: (c) => <span>{c}</span> })}</p>
<p>{t.rich('downloadAddons.step3', { s: (c) => <span>{c}</span> })}</p>
<br />
<hr />
<br />
@@ -44,11 +46,11 @@ export default function DownloadAddonsPage() {
<tr key={a.name} className="team-center-table-tr">
<td className="lefted separate">
<span className="yellow-info">{a.name}</span>
<p className="third-brown small-font">{a.desc}</p>
<p className="third-brown small-font">{t(`downloadAddons.${a.descKey}`)}</p>
</td>
<td className="real-info-box no-wrap-td">
<a href={a.url} target="_blank" rel="noopener noreferrer" className="nw-tool-btn">
Descargar
{t('downloadAddons.download')}
</a>
</td>
</tr>
+9 -15
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -24,34 +24,28 @@ export default async function GoldCharacterPage({ params }: { params: Promise<{
const [chars, options] = await Promise.all([getGameCharacters(session.accountId!), getGoldOptions()])
const t = await getTranslations('CharServiceB.gold')
return (
<PageShell title="Adquirir oro">
<ServiceBox>
<br />
<p>
La herramienta <span>Adquirir oro</span> te permite enviar oro a un personaje.
</p>
<p>{t.rich('intro', { s: (c) => <span>{c}</span> })}</p>
<br />
<p>
Al usar el botón ENVIAR ORO del personaje que hayas escogido, se enviará un correo con la cantidad de oro
elegida al personaje.
</p>
<p>Cuando entres al reino con dicho personaje, tendrás el mensaje con la cantidad de oro seleccionada en tu buzón.</p>
<p>{t('instructions')}</p>
<p>{t('mailInfo')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('noteLegend')}</legend>
<div className="separate2">
<p>
Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez
realizada.
</p>
<p>{t('noteConfirm')}</p>
</div>
</fieldset>
<div className="centered">
<br />
<br />
<p>Requiere</p>
<p>{t('requiresLabel')}</p>
<table className="gold-table">
<tbody>
{options.map((o) => (
+56 -57
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { getRealmName } from '@/lib/realm'
export const metadata: Metadata = { title: 'Ayuda' }
@@ -9,6 +9,7 @@ export const dynamic = 'force-dynamic'
export default async function HelpPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Misc')
const realm = await getRealmName()
return (
@@ -16,80 +17,78 @@ export default async function HelpPage({ params }: { params: Promise<{ locale: s
<div className="middle-content">
<div className="body-content">
<div className="title-content">
<h1>Ayuda</h1>
<h1>{t('help.title')}</h1>
</div>
<div className="box-content">
<div className="title-box-content">
<h2>Guía de asistencia</h2>
<h2>{t('help.subtitle')}</h2>
</div>
<div className="body-box-content justified">
<p>En esta guía podrás saber qué pasos seguir para obtener asistencia adecuada dependiendo del tipo de problema que hayas encontrado en nuestro servidor.</p>
<p>Ofrecemos un conjunto de problemas comunes, qué medios utilizar para cada caso y una explicación de cada medio de contacto.</p>
<p>Los diferentes medios para cada problema se listan desde el más ideal hasta la última opción.</p>
<p>{t('help.intro1')}</p>
<p>{t('help.intro2')}</p>
<p>{t('help.intro3')}</p>
<br />
<span>Con el cliente</span>
<p>Problemas que impiden arrancar el cliente, conectar al servidor o errores que provocan el cierre abrupto del cliente.</p>
<p>Los medios son:</p>
<p>- Foros</p>
<p>- Discord</p>
<span>{t('help.clientTitle')}</span>
<p>{t('help.clientText')}</p>
<p>{t('help.mediaAre')}</p>
<p>{t('help.mediaForos')}</p>
<p>{t('help.mediaDiscord')}</p>
<br />
<span>Con errores del servidor</span>
<p>Problemas relacionados a errores que deben ser reparados por nuestro equipo de reparación para mejorar el servidor.</p>
<p>Los medios son:</p>
<p>- Foros</p>
<span>{t('help.serverErrorsTitle')}</span>
<p>{t('help.serverErrorsText')}</p>
<p>{t('help.mediaAre')}</p>
<p>{t('help.mediaForos')}</p>
<br />
<span>Con el personaje</span>
<p>Problemas relacionados a errores que puedan afectar al personaje, habilidades, hechizos, misiones, etc.</p>
<p>Los medios son:</p>
<p>- Ticket</p>
<p>- Discord</p>
<span>{t('help.characterTitle')}</span>
<p>{t('help.characterText')}</p>
<p>{t('help.mediaAre')}</p>
<p>{t('help.mediaTicket')}</p>
<p>{t('help.mediaDiscord')}</p>
<br />
<span>Con otro jugador</span>
<p>Problemas relacionados a otros usuarios cometiendo faltas a las normas del servidor.</p>
<p>Este tipo de problemas requieren de una denuncia con las pruebas correspondientes de lo que haya sucedido.</p>
<p>Los medios son:</p>
<p>- Foros</p>
<span>{t('help.otherPlayerTitle')}</span>
<p>{t('help.otherPlayerText1')}</p>
<p>{t('help.otherPlayerText2')}</p>
<p>{t('help.mediaAre')}</p>
<p>{t('help.mediaForos')}</p>
<br />
<span>Con bloqueos de cuenta</span>
<p>Problemas relacionados a bloqueos de cuenta realizados por el equipo de {realm}.</p>
<p>Los medios son:</p>
<p>- Foros</p>
<span>{t('help.accountBlocksTitle')}</span>
<p>{t('help.accountBlocksText', { realm })}</p>
<p>{t('help.mediaAre')}</p>
<p>{t('help.mediaForos')}</p>
<br />
<span>Con la página web</span>
<p>Problemas relacionados a errores, fallas o sugerencias respecto de este sitio web.</p>
<p>Los medios son:</p>
<p>- Correo a <a href="mailto:ryuzaki@ultimowow.com">ryuzaki@ultimowow.com</a></p>
<span>{t('help.websiteTitle')}</span>
<p>{t('help.websiteText')}</p>
<p>{t('help.mailTo')} <a href="mailto:ryuzaki@ultimowow.com">ryuzaki@ultimowow.com</a></p>
<br />
<span>Con PD</span>
<p>Problemas relacionados a PD o PV.</p>
<p>Los medios son:</p>
<p>- Correo a <a href="mailto:contacto@ultimowow.com">contacto@ultimowow.com</a></p>
<span>{t('help.pdTitle')}</span>
<p>{t('help.pdText')}</p>
<p>{t('help.mailTo')} <a href="mailto:contacto@ultimowow.com">contacto@ultimowow.com</a></p>
<br />
<fieldset>
<legend>Medios de contacto</legend>
<legend>{t('help.contactMeans')}</legend>
<div className="separate2">
<span>Ticket</span>
<p>Para colocar un ticket, debes hacer click al signo de interrogación rojo en la parte inferior derecha de la interfaz del cliente (a la izquierda del llavero del personaje).</p>
<p>Al hacerle click, aparecerá una ventana en el medio de la pantalla.</p>
<p>En dicha ventana, hacer click al primer botón en la esquina inferior izquierda llamado &apos;Hablar con un MJ&apos;</p>
<p>Aparecerá una ventana con un texto informativo y debajo en medio, un botón llamado &apos;Abrir una consulta&apos;.</p>
<p>Al darle click al botón, aparecerá un campo de texto en el que puedes describir tu problema</p>
<p>Cuando hayas finalizado haz click en el botón en el primer botón en la esquina inferior derecha llamado &apos;Enviar&apos;.</p>
<p>El ticket ya ha sido creado y está pendiente de respuesta de un miembro del equipo.</p>
<span>{t('help.ticketTitle')}</span>
<p>{t('help.ticket1')}</p>
<p>{t('help.ticket2')}</p>
<p>{t('help.ticket3')}</p>
<p>{t('help.ticket4')}</p>
<p>{t('help.ticket5')}</p>
<p>{t('help.ticket6')}</p>
<p>{t('help.ticket7')}</p>
<br />
<span>Discord</span>
<p>Discord es una plataforma de chat y comunicación por voz que puede descargarse <a href="https://discordapp.com/" target="_blank" rel="noopener noreferrer">aquí</a>.</p>
<p>Para entrar en el servidor de Discord de nuestro servidor, haz click <a href="https://discord.ultimowow.com" target="_blank" rel="noopener noreferrer">aquí</a>.</p>
<p>Una vez que seas miembro de nuestro Discord podrás pedir ayuda en el canal &apos;Asistencia&apos; o enviar un mensaje directo a cualquier miembro del equipo solicitando ayuda.</p>
<span>{t('help.discordTitle')}</span>
<p>{t.rich('help.discord1', { link: (c) => <a href="https://discordapp.com/" target="_blank" rel="noopener noreferrer">{c}</a> })}</p>
<p>{t.rich('help.discord2', { link: (c) => <a href="https://discord.ultimowow.com" target="_blank" rel="noopener noreferrer">{c}</a> })}</p>
<p>{t('help.discord3')}</p>
<br />
<span>Foros</span>
<p>Los foros son una gran herramienta en donde no sólo puede ayudarte el equipo de {realm} sino que además otros jugadores pueden asistirte si conocen cómo resolver el problema que tengas.</p>
<p>Para solicitar ayuda por este medio, debes tener un usuario con el cual poder conectar y abrir un nuevo tema en el foro adecuado.</p>
<p>Puedes visitar nuestros foros <a href="https://foro.ultimowow.com/" target="_blank" rel="noopener noreferrer">aquí</a>.</p>
<span>{t('help.forosTitle')}</span>
<p>{t('help.foros1', { realm })}</p>
<p>{t('help.foros2')}</p>
<p>{t.rich('help.foros3', { link: (c) => <a href="https://foro.ultimowow.com/" target="_blank" rel="noopener noreferrer">{c}</a> })}</p>
<br />
<span>Correo electrónico</span>
<p>El servidor tiene diferentes direcciones de correo dedicados a diferentes temas del servidor.</p>
<p>Depende cual sea tu problema, usa el adecuado para enviarnos un mensaje de correo.</p>
<span>{t('help.emailTitle')}</span>
<p>{t('help.email1')}</p>
<p>{t('help.email2')}</p>
</div>
</fieldset>
</div>
+10 -8
View File
@@ -1,30 +1,32 @@
import type { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
export const metadata: Metadata = {
title: 'Aviso legal',
}
export default function LegalNoticePage() {
export default async function LegalNoticePage() {
const t = await getTranslations('Legal')
return (
<div className="main-page">
<div className="middle-content">
<div className="body-content">
<div className="title-content">
<h1>Aviso legal</h1>
<h1>{t('legalNotice.title')}</h1>
</div>
<div className="box-content">
<div className="body-box-content">
<p>El sitio web &quot;UltimoWoW.com&quot; y sus subdominios (en adelante, &quot;el sitio web&quot;) se ofrecen únicamente con fines informativos y de entretenimiento. UltimoWoW no se hace responsable del uso que los usuarios hagan del sitio web. Aunque se procura que se un uso correcto, UltimoWoW no asume responsabilidad por las acciones, decisiones o consecuencias derivadas del uso del sitio web. Los usuarios son responsables de verificar la precisión, integridad y utilidad de la información proporcionada, así como de cumplir con las leyes y regulaciones aplicables al utilizar el contenido del sitio web.</p>
<p>{t('legalNotice.p1')}</p>
<br />
<p>Enlaces a terceros: El sitio web puede contener enlaces a sitios web de terceros. Estos enlaces se proporcionan únicamente para tu conveniencia y no implican el respaldo o afiliación de UltimoWoW con dichos sitios web. UltimoWoW no se hace responsable del contenido o las prácticas de privacidad de estos sitios web de terceros.</p>
<p>{t('legalNotice.p2')}</p>
<br />
<p>Privacidad y Cookies: La recopilación y uso de tus datos personales se rigen por nuestra Política de Privacidad y el uso de cookies se rige por nuestra Declaración de Cookies. Al utilizar el sitio web, aceptas nuestras prácticas descritas en dichas políticas.</p>
<p>{t('legalNotice.p3')}</p>
<br />
<p>Derechos de autor: Los usuarios del sitio web deben respetar los derechos de autor y no publicar contenido que infrinja los derechos de propiedad intelectual de terceros. UltimoWoW se compromete a proteger los derechos de autor y cumplir con las leyes aplicables. Si encuentras contenido infractor, contáctanos utilizando el mecanismo de reclamación de infracción descrito a continuación.</p>
<p>{t('legalNotice.p4')}</p>
<br />
<p>Mecanismo de reclamación de infracción: Si crees que tu trabajo ha sido utilizado de manera no autorizada en el sitio web, puedes presentar una reclamación por infracción de derechos de autor proporcionando la información requerida. Contáctanos utilizando los datos de contacto a continuación.</p>
<p>{t('legalNotice.p5')}</p>
<br />
<p className="second-brown">Contacto</p>
<p className="second-brown">{t('legalNotice.contactTitle')}</p>
<p>ultimowow.com</p>
<p>info@ultimowow.com</p>
</div>
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -22,44 +22,39 @@ export default async function LevelUpCharacterPage({ params }: { params: Promise
const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getLevelUpPrice()])
const t = await getTranslations('CharServiceB.levelUp')
return (
<PageShell title="Subir a nivel 80">
<ServiceBox>
<br />
<p>
La herramienta <span>Subir a nivel 80</span> te permite subir cualquier personaje al nivel 80.
</p>
<p>Sólo subirá de nivel al personaje. Esta opción no incluye ítems/oro/etc.</p>
<p>{t.rich('intro', { s: (c) => <span>{c}</span> })}</p>
<p>{t('onlyLevel')}</p>
<br />
<p>
Al usar el botón SUBIR A NIVEL 80 del personaje que hayas escogido, se enviará el nivel 80 al personaje.
</p>
<p>Cuando ingreses a la lista de personajes de tu cuenta, verás que tu personaje escogido ya es nivel 80.</p>
<p>{t('instructions')}</p>
<p>{t('levelInfo')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('noteLegend')}</legend>
<div className="separate2">
<p>Se requiere que el personaje esté desconectado.</p>
<p>
Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez
realizada.
</p>
<p>{t('noteOffline')}</p>
<p>{t('noteConfirm')}</p>
</div>
</fieldset>
<div className="centered">
<br />
<br />
<p>Requiere <span>{price}</span> <span className="yellow-info"></span> (SumUp)</p>
<p>{t.rich('requires', { price, s: (c) => <span>{c}</span>, y: (c) => <span className="yellow-info">{c}</span> })}</p>
</div>
<PaidServiceForm
characters={chars.map((c) => ({ name: c.name, classCss: c.classCss }))}
checkoutEndpoint="/api/character/level-up/checkout"
payLabel="SUBIR A NIVEL 80"
payLabel={t('payLabel')}
buttonClass="level-up-button"
provider="sumup"
confirmText={`¿Estás seguro de subir a nivel 80 al personaje seleccionado por ${price} € (SumUp)?`}
confirmText={t('confirm', { price })}
/>
</ServiceBox>
</PageShell>
+14 -23
View File
@@ -1,4 +1,4 @@
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { Link } from '@/i18n/navigation'
import { ServerClock } from '@/components/ServerClock'
import { getNoticias, getServerStatus, type Noticia, type ServerStatus } from '@/lib/home'
@@ -38,6 +38,7 @@ function formatFecha(iso: string | Date | null): string {
export default async function HomePage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Misc')
const { noticias, status } = await getData()
const realmName = await getRealmName()
@@ -72,7 +73,7 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
<td className={`real-info-box ${loginClass}`}>{loginStatus}</td>
</tr>
<tr>
<td>Hora</td>
<td>{t('home.labelTime')}</td>
<td className="real-info-box">
<ServerClock />
</td>
@@ -93,42 +94,32 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
</table>
</div>
<div className="raf-index-responsive">
<Link href="/recruit-a-friend">¡RECLUTA A UN AMIGO!</Link>
<Link href="/recruit-a-friend">{t('home.recruitFriend')}</Link>
</div>
</div>
<div className="raf-index">
<Link href="/recruit-a-friend">¡RECLUTA A UN AMIGO!</Link>
<Link href="/recruit-a-friend">{t('home.recruitFriend')}</Link>
</div>
</div>
<div className="body-box-content">
<div className="body-box-content">
<p>
{realmName} 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>
<p>{t('home.introP1', { realm: realmName })}</p>
<p>{t('home.introP2')}</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>
<p>{t('home.introP3')}</p>
<p>{t('home.introP4')}</p>
</div>
</div>
</div>
<br />
<div className="title-content-h3">
<h3 className="big-font">NOTICIAS</h3>
<h3 className="big-font">{t('home.newsHeading')}</h3>
</div>
<div className="box-content">
<div className="body-box-content">
{noticias.length === 0 ? (
<p>No hay noticias disponibles en este momento.</p>
<p>{t('home.noNews')}</p>
) : (
<>
{noticias.map((n) => (
@@ -141,9 +132,9 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
/>
{n.enlace && (
<p>
Enlace:{' '}
{t('home.newsLinkLabel')}{' '}
<a href={n.enlace} target="_blank" rel="noopener noreferrer">
aquí
{t('home.here')}
</a>
</p>
)}
@@ -153,7 +144,7 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
</div>
))}
<p className="centered third-brown">
<i>* Mostrando las últimas 50 noticias</i>
<i>{t('home.showingLatest')}</i>
</p>
<br />
</>
+14 -14
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { setRequestLocale, getTranslations } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getPointsHistory, getPointsBalances, type PointsMovement } from '@/lib/points-history'
@@ -25,6 +25,7 @@ function methodClass(method: PointsMovement['method']): string {
export default async function PointsHistoryPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('History')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -36,25 +37,24 @@ export default async function PointsHistoryPage({ params }: { params: Promise<{
])
return (
<PageShell title="Historial de PD y PV">
<PageShell title={t('points.pageTitle')}>
<ServiceBox>
<br />
<p>Aquí verás los consumos de PD y PV más recientes que ha tenido la cuenta.</p>
<p>{t('points.intro1')}</p>
<p>
Con esta información podrás tener control de todo lo que has adquirido así como también chequear que toda la
actividad sea normal.
{t('points.intro2')}
</p>
<p>Si observas algún movimiento sospechoso, puedes contactar con el equipo de Nova WoW.</p>
<p>{t('points.intro3')}</p>
<br />
</ServiceBox>
<div className="box-content">
<div className="title-box-content">
<h2>Historial de PD y PV</h2>
<h2>{t('points.boxTitle')}</h2>
</div>
<div className="body-box-content">
<p className="centered">
Saldo actual: <span className="yellow-info">{balances.dp} PD</span> ·{' '}
{t('points.currentBalance')} <span className="yellow-info">{balances.dp} PD</span> ·{' '}
<span className="purple-info">{balances.pv} PV</span>
</p>
<br />
@@ -63,7 +63,7 @@ export default async function PointsHistoryPage({ params }: { params: Promise<{
<tbody>
<tr>
<td>
<span>No hay movimientos</span>
<span>{t('points.noMovements')}</span>
</td>
</tr>
</tbody>
@@ -72,13 +72,13 @@ export default async function PointsHistoryPage({ params }: { params: Promise<{
<table className="max-center-table-aligned2">
<thead>
<tr>
<th>Fecha</th>
<th>Concepto</th>
<th>Método</th>
<th>{t('points.thDate')}</th>
<th>{t('points.thConcept')}</th>
<th>{t('points.thMethod')}</th>
<th>PD</th>
<th>PV</th>
<th>Importe</th>
<th>Estado</th>
<th>{t('points.thAmount')}</th>
<th>{t('points.thStatus')}</th>
</tr>
</thead>
<tbody>
+33 -31
View File
@@ -1,76 +1,78 @@
import type { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
export const metadata: Metadata = {
title: 'Política de privacidad',
}
export default function PrivacyPolicyPage() {
export default async function PrivacyPolicyPage() {
const t = await getTranslations('Legal')
return (
<div className="main-page">
<div className="middle-content">
<div className="body-content">
<div className="title-content">
<h1>Política de privacidad</h1>
<h1>{t('privacy.title')}</h1>
</div>
<div className="box-content">
<div className="body-box-content justified">
<p>Esta Política de Privacidad describe cómo recopilamos, utilizamos, almacenamos y protegemos la información personal de los usuarios (&quot;usted&quot; o &quot;usuario&quot;) de nuestro sitio web. Al utilizar nuestro sitio web, usted acepta las prácticas descritas en esta política.</p>
<p>{t('privacy.p1')}</p>
<br />
<hr />
<br />
<p className="second-brown">Recopilación de Información Personal</p>
<p>Recopilamos la siguiente información personal cuando usted utiliza nuestro sitio web:</p>
<p>1. Cookies: Utilizamos cookies para recopilar información sobre su actividad en nuestro sitio web. Las cookies son pequeños archivos de texto que se almacenan en su dispositivo y nos permiten reconocer su navegador o dispositivo y recordar cierta información. Estas cookies pueden ser cookies de sesión o cookies persistentes. Puede ajustar la configuración de su navegador para rechazar todas las cookies o para indicar cuándo se envía una cookie. Sin embargo, si desactiva las cookies, es posible que algunas funciones de nuestro sitio web no funcionen correctamente.</p>
<p>2. Direcciones IP: Recopilamos direcciones IP de los usuarios que visitan nuestro sitio web. Las direcciones IP se utilizan para analizar tendencias, administrar el sitio web, rastrear el movimiento del usuario y recopilar información demográfica. No vinculamos las direcciones IP con ninguna información de identificación personal.</p>
<p>3. Direcciones de correo electrónico: Recopilamos las direcciones de correo electrónico que usted proporciona al registrarse en nuestro sitio web o al actualizar su información de registro. Utilizamos estas direcciones de correo electrónico para enviarle información relacionada con su cuenta, como confirmaciones de registro, notificaciones de cambios en los términos o políticas, y actualizaciones de seguridad.</p>
<p>4. Nombres de usuario y contraseñas: Recopilamos los nombres de usuario y las contraseñas que usted elige al registrar una cuenta en nuestro sitio web. Estas credenciales se utilizan para autenticar su acceso a su cuenta y para proteger su información personal.</p>
<p className="second-brown">{t('privacy.h1')}</p>
<p>{t('privacy.p2')}</p>
<p>{t('privacy.p3')}</p>
<p>{t('privacy.p4')}</p>
<p>{t('privacy.p5')}</p>
<p>{t('privacy.p6')}</p>
<br />
<hr />
<br />
<p className="second-brown">Uso de la Información Personal</p>
<p>Utilizamos la información personal que recopilamos para los siguientes fines:</p>
<p>1. Brindar y mejorar nuestros servicios: Utilizamos su información personal para brindarle los servicios solicitados y mejorar su experiencia en nuestro sitio web. Esto incluye personalizar el contenido y las recomendaciones, proporcionar soporte técnico, y analizar el rendimiento y la eficacia de nuestro sitio web.</p>
<p>2. Comunicaciones: Podemos utilizar su dirección de correo electrónico para enviarle comunicaciones relacionadas con su cuenta, como confirmaciones de registro, notificaciones de cambios en los términos o políticas, actualizaciones de seguridad y otras actualizaciones importantes.</p>
<p className="second-brown">{t('privacy.h2')}</p>
<p>{t('privacy.p7')}</p>
<p>{t('privacy.p8')}</p>
<p>{t('privacy.p9')}</p>
<br />
<hr />
<br />
<p className="second-brown">Protección de la Información Personal</p>
<p>Tomamos medidas razonables para proteger la información personal que recopilamos y almacenamos. Utilizamos medidas de seguridad técnicas, administrativas y físicas para proteger su información contra el acceso no autorizado, la divulgación, la alteración o la destrucción.</p>
<p className="second-brown">{t('privacy.h3')}</p>
<p>{t('privacy.p10')}</p>
<br />
<hr />
<br />
<p className="second-brown">Divulgación de la Información Personal a Terceros</p>
<p>No vendemos, intercambiamos ni transferimos su información personal a terceros sin su consentimiento, excepto en los siguientes casos:</p>
<p>- Proveedores de servicios: Podemos compartir su información personal con proveedores de servicios que realizan funciones en nuestro nombre, como el alojamiento del sitio web, el procesamiento de pagos y el análisis de datos. Estos proveedores de servicios tienen acceso a su información personal solo en la medida necesaria para realizar sus funciones y están obligados a no divulgar ni utilizar la información con ningún otro fin.</p>
<p>- Cumplimiento legal: Podemos divulgar su información personal si creemos de buena fe que dicha divulgación es necesaria para cumplir con una obligación legal, proteger sus derechos o seguridad, investigar fraudes o responder a una solicitud legal.</p>
<p className="second-brown">{t('privacy.h4')}</p>
<p>{t('privacy.p11')}</p>
<p>{t('privacy.p12')}</p>
<p>{t('privacy.p13')}</p>
<br />
<hr />
<br />
<p className="second-brown">Sus Derechos y Opciones</p>
<p>Usted tiene ciertos derechos y opciones con respecto a su información personal:</p>
<p>- Acceso y actualización: Usted puede acceder y actualizar cierta información personal proporcionada a través de su cuenta en nuestro sitio web.</p>
<p>- Eliminación de datos: Usted puede solicitar la eliminación de su información personal de nuestros registros, sujeto a cualquier requisito legal o contractual que podamos tener.</p>
<p>- Cookies: Puede ajustar la configuración de su navegador para rechazar cookies o para indicar cuándo se envía una cookie. Sin embargo, tenga en cuenta que deshabilitar las cookies puede afectar el funcionamiento de nuestro sitio web.</p>
<p className="second-brown">{t('privacy.h5')}</p>
<p>{t('privacy.p14')}</p>
<p>{t('privacy.p15')}</p>
<p>{t('privacy.p16')}</p>
<p>{t('privacy.p17')}</p>
<br />
<hr />
<br />
<p className="second-brown">Uso incorrecto de plataformas de terceros con el fin de violar nuestra política de devolución</p>
<p>Este tipo de acciones conllevará a la expulsión del usuario de nuestro sitio sin ningún tipo de devolución.</p>
<p className="second-brown">{t('privacy.h6')}</p>
<p>{t('privacy.p18')}</p>
<br />
<hr />
<br />
<p className="second-brown">Cambios a esta Política de Privacidad</p>
<p>Nos reservamos el derecho de actualizar esta Política de Privacidad en cualquier momento. Le recomendamos que revise periódicamente esta página para conocer los cambios. La fecha de entrada en vigencia de la política se indicará en la parte superior de la página.</p>
<p className="second-brown">{t('privacy.h7')}</p>
<p>{t('privacy.p19')}</p>
<br />
<hr />
<br />
<p className="second-brown">Contáctanos</p>
<p>Si tiene alguna pregunta o inquietud sobre esta Política de Privacidad, puede contactarnos a través de la siguiente información de contacto:</p>
<p className="second-brown">{t('privacy.h8')}</p>
<p>{t('privacy.p20')}</p>
<p>Error 404</p>
<p>ultimowow.com</p>
<p>info@ultimowow.com</p>
<br />
<p className="grey-info2">Fecha de actualización: 23-05-2023</p>
<p className="grey-info2">{t('privacy.updated')}</p>
</div>
</div>
</div>
+8 -10
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getRealmName } from '@/lib/realm'
@@ -14,6 +14,7 @@ export const metadata: Metadata = { title: 'Código de promoción' }
export default async function PromoCodePage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Points')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -22,25 +23,22 @@ export default async function PromoCodePage({ params }: { params: Promise<{ loca
const realm = await getRealmName()
return (
<PageShell title="Código de promoción">
<PageShell title={t('promo.title')}>
<ServiceBox>
<br />
<p>
Los <span>Códigos de promoción</span> son palabras con letras y números que se canjean en el formulario de
abajo por PD o PV y tienen usos limitados. Cada uno puede dar diferente cantidad de PD/PV.
{t.rich('promo.intro1', { s: (c) => <span>{c}</span> })}
</p>
<p>
Estos códigos son generados por el Equipo de {realm} y pueden aparecer en la página web, Discord, las redes
sociales o el servidor.
{t('promo.intro2', { realm })}
</p>
<p>Aconsejamos poner atención a las redes sociales y los anuncios del servidor para saber cuándo hay un código disponible.</p>
<p>{t('promo.intro3')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('promo.note')}</legend>
<div className="separate2">
<p>
Los códigos son sensibles a mayúsculas y minúsculas por lo que siempre debes escribirlos igual a como
aparecen publicados.
{t('promo.noteText')}
</p>
</div>
</fieldset>
+26 -24
View File
@@ -1,5 +1,6 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { NoteLegend } from '@/components/NoteLegend'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -25,6 +26,7 @@ const CLASS_GROUPS: { css: string; name: string }[] = [
export default async function QuestCharacterPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('CharService')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -40,21 +42,20 @@ export default async function QuestCharacterPage({ params }: { params: Promise<{
}))
return (
<PageShell title="Rastreador de misiones">
<PageShell title={t('quest.pageTitle')}>
<ServiceBox>
<br />
<p>
La herramienta <span>Rastreador de misiones</span> te permite ver el estado de misiones o cadenas de misiones
importantes en tus personajes.
{t.rich('quest.intro', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>Opciones disponibles:</p>
<p>{t('quest.optionsAvailable')}</p>
{CLASS_GROUPS.map((g) => {
const opts = QUEST_OPTIONS.filter((o) => o.classCss === g.css)
if (opts.length === 0) return null
return (
<div key={g.css}>
<p className={g.css}>{g.name}</p>
<p className={g.css}>{t(`quest.className.${g.css}`)}</p>
<ul>
{opts.map((o) => (
<li key={o.key}>
@@ -78,7 +79,7 @@ export default async function QuestCharacterPage({ params }: { params: Promise<{
) : (
o.label
)}{' '}
a partir del nivel {o.minLevel}
{t('quest.fromLevel', { level: o.minLevel })}
</li>
))}
</ul>
@@ -86,35 +87,36 @@ export default async function QuestCharacterPage({ params }: { params: Promise<{
</div>
)
})}
<p className="second-brown">Por reputación</p>
<p className="second-brown">{t('quest.byReputation')}</p>
<ul>
<li>
<a href={`${QUEST_DB}/?faction=1119`} target="_blank" rel="noopener noreferrer">Los Hijos de Hodir</a>: Cadena
de misiones principal hasta interactuar con el intendente{' '}
<a href={`${QUEST_DB}/?npc=32540`} target="_blank" rel="noopener noreferrer">Lillehoff</a>
{t.rich('quest.hodir', {
hodir: (c) => <a href={`${QUEST_DB}/?faction=1119`} target="_blank" rel="noopener noreferrer">{c}</a>,
lille: (c) => <a href={`${QUEST_DB}/?npc=32540`} target="_blank" rel="noopener noreferrer">{c}</a>,
})}
</li>
</ul>
<br />
<p className="second-brown">Por misión específica</p>
<p className="second-brown">{t('quest.bySpecific')}</p>
<ul>
<li>A partir del nivel 10</li>
<li>{t('quest.fromLevel10')}</li>
</ul>
<br />
<p>Cómo buscar por misión específica:</p>
<p>1) Visita nuestra <a href={QUEST_DB} target="_blank" rel="noopener noreferrer">base de datos</a>.</p>
<p>2) Busca allí el nombre de la misión.</p>
<p>3) Una vez que la hayas encontrado, su enlace se verá cómo &quot;<span className="second-brown">{QUEST_DB}/?quest=12843</span>&quot;</p>
<p>4) Usa el número final del enlace en el campo ID de misión (Ejemplo del enlace anterior: 12843).</p>
<p>{t('quest.howToSearch')}</p>
<p>{t.rich('quest.step1', { link: (c) => <a href={QUEST_DB} target="_blank" rel="noopener noreferrer">{c}</a> })}</p>
<p>{t('quest.step2')}</p>
<p>{t.rich('quest.step3', { url: QUEST_DB, s: (c) => <span className="second-brown">{c}</span> })}</p>
<p>{t('quest.step4')}</p>
<br />
<p>Al usar el botón BUSCAR MISIONES, podrás ver el estado de las misiones del personaje.</p>
<p>{t('quest.searchButtonExplain')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<NoteLegend />
<div className="separate2">
<p>Se requiere que el personaje esté desconectado y cumpla con el mínimo de nivel de cada opción.</p>
<p>Se puede repetir búsqueda de cadenas de misiones en un mismo personaje cada 1 hora.</p>
<p>Se puede repetir búsqueda de cadenas de misiones de clase en un mismo personaje cada 30 minutos.</p>
<p>Se puede repetir búsqueda de misión específica en un mismo personaje cada 10 minutos.</p>
<p>{t('quest.noteOffline')}</p>
<p>{t('quest.noteChain1h')}</p>
<p>{t('quest.noteClass30')}</p>
<p>{t('quest.noteSpecific10')}</p>
</div>
</fieldset>
+52 -50
View File
@@ -1,104 +1,106 @@
import type { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
export const metadata: Metadata = {
title: 'Política de reembolso',
}
export default function RefundPolicyPage() {
export default async function RefundPolicyPage() {
const t = await getTranslations('Legal')
return (
<div className="main-page">
<div className="middle-content">
<div className="body-content">
<div className="title-content">
<h1>Política de reembolso</h1>
<h1>{t('refund.title')}</h1>
</div>
<div className="box-content">
<div className="body-box-content justified">
<p className="second-brown">NUESTRO SERVICIO</p>
<p>Todos nuestros servicios son de entrega inmediata y se reciben tal cual se encuentran descritos.</p>
<p className="second-brown">{t('refund.h1')}</p>
<p>{t('refund.p1')}</p>
<br />
<hr />
<br />
<p className="second-brown">NUESTRA RESPONSABILIDAD</p>
<p>Garantizamos el funcionamiento, tiempo de entrega y descripción de los servicios brindados por nuestro sitio.</p>
<p>Es importante que el usuario comprenda que lo que adquiere en el sitio son servicios que se reciben de forma inmediata.</p>
<p>No adquiere tiempo de uso de los servicios, ni beneficio alguno, ni inmunidad.</p>
<p>Todo caso avalado por nuestra política de devolución será tratado con el reinicio o corrección del servicio y posterior revisión garantizando el funcionamiento correcto del servicio reiniciado o corregido.</p>
<p className="second-brown">{t('refund.h2')}</p>
<p>{t('refund.p2')}</p>
<p>{t('refund.p3')}</p>
<p>{t('refund.p4')}</p>
<p>{t('refund.p5')}</p>
<br />
<hr />
<br />
<p className="second-brown">TIEMPO DE NOTIFICACIÓN</p>
<p>Cualquier error o problema de nuestros servicios debe ser notificado a la administración en un período máximo de 48 horas desde la entrega del servicio.</p>
<p>Pasado este período de tiempo, el usuario ya no puede solicitar ningún tipo de devolución.</p>
<p>Contacto: <a href="mailto:contacto@ultimowow.com">contacto@ultimowow.com</a>.</p>
<p className="second-brown">{t('refund.h3')}</p>
<p>{t('refund.p6')}</p>
<p>{t('refund.p7')}</p>
<p>{t.rich('refund.p8', { link: (c) => <a href="mailto:contacto@ultimowow.com">{c}</a> })}</p>
<br />
<hr />
<br />
<p className="second-brown">COBERTURA</p>
<p>Esta política de devolución cubre:</p>
<p>- Adquisición del servicio incorrecto por una mala selección del usuario.</p>
<p>- Errores ocasionados por el sitio web que hayan impedido la entrega del servicio.</p>
<p>- Errores ocasionados por plataformas de terceros que hayan impedido la entrega del servicio.</p>
<p className="second-brown">{t('refund.h4')}</p>
<p>{t('refund.p9')}</p>
<p>{t('refund.p10')}</p>
<p>{t('refund.p11')}</p>
<p>{t('refund.p12')}</p>
<br />
<p>Esta política de devolución NO cubre, bajo ninguna circunstancia:</p>
<p>- Arrepentimientos.</p>
<p>- Errores en el uso del servicio realizados por el usuario.</p>
<p>- Desconocimiento de nuestra política de devolución.</p>
<p>- Uso incorrecto de plataformas de terceros con el fin de violar nuestra política de devolución.</p>
<p>- Expulsión de nuestro sitio por incumplimiento de nuestros términos y normas.</p>
<p>{t('refund.p13')}</p>
<p>{t('refund.p14')}</p>
<p>{t('refund.p15')}</p>
<p>{t('refund.p16')}</p>
<p>{t('refund.p17')}</p>
<p>{t('refund.p18')}</p>
<br />
<p className="second-brown">Solicitud de reembolso hecha a través de la plataforma de pago.</p>
<p>Pedir un reembolso por razones avaladas en esta política de devolución procederá a un reembolso de nuestra parte con la correspondiente cancelación del servicio adquirido.</p>
<p className="second-brown">{t('refund.h5')}</p>
<p>{t('refund.p19')}</p>
<br />
<p>Pedir un reembolso por razones no avaladas en esta política de devolución como:</p>
<p>- La cuenta ha sido sancionada.</p>
<p>- La persona ya no usa el servicio e intenta solicitar un reembolso posterior a las 48 hs del momento de la adquisición.</p>
<p>- Abuso de herramientas disponibles alegando razones que falten a la verdad respecto del servicio entregado.</p>
<p>No aplican para devolución y puede conllevar a la expulsión permanente del sitio por falta a nuestros términos y un uso inadecuado de las plataformas de pago disponibles.</p>
<p>{t('refund.p20')}</p>
<p>{t('refund.p21')}</p>
<p>{t('refund.p22')}</p>
<p>{t('refund.p23')}</p>
<p>{t('refund.p24')}</p>
<br />
<hr />
<br />
<p className="second-brown">Arrepentimientos</p>
<p>Ningún tipo de arrepentimiento posterior a la entrega de un servicio aplica para devolución.</p>
<p className="second-brown">{t('refund.h6')}</p>
<p>{t('refund.p25')}</p>
<br />
<hr />
<br />
<p className="second-brown">Errores en el uso del servicio realizados por el usuario</p>
<p>Errores en el uso de los servicios, ocasionados por el usuario, que no tienen relación con ningún tipo de falla en el servicio entregado no aplica para devolución.</p>
<p>Cualquier error de usuario ocasionado por no leer la descripción de un servicio no aplica para devolución.</p>
<p>Una vez que el servicio se encuentra entregado, el uso del mismo queda a responsabilidad del usuario.</p>
<p className="second-brown">{t('refund.h7')}</p>
<p>{t('refund.p26')}</p>
<p>{t('refund.p27')}</p>
<p>{t('refund.p28')}</p>
<br />
<hr />
<br />
<p className="second-brown">Desconocimiento de nuestra política de devolución</p>
<p>El desconocimiento por parte del usuario de nuestra política de devolución no exime al usuario de su cumplimiento.</p>
<p className="second-brown">{t('refund.h8')}</p>
<p>{t('refund.p29')}</p>
<br />
<hr />
<br />
<p className="second-brown">Uso incorrecto de plataformas de terceros con el fin de violar nuestra política de devolución</p>
<p>Este tipo de acciones conllevará a la expulsión del usuario de nuestro sitio sin ningún tipo de devolución.</p>
<p className="second-brown">{t('refund.h9')}</p>
<p>{t('refund.p30')}</p>
<br />
<hr />
<br />
<p className="second-brown">Expulsión de nuestro sitio por incumplimiento de nuestros términos y normas</p>
<p>Ninguna adquisición de nuestros servicios otorga ningún tipo de inmunidad.</p>
<p>La expulsión de un usuario de nuestro sitio no aplica a ningún tipo de devolución.</p>
<p>El cumplimiento de nuestros términos y normas queda a exclusiva responsabilidad del usuario y cualquier expulsión que resulte del incumplimiento de los mismos no califica a solicitar devolución de los servicios ya entregados y disfrutados.</p>
<p className="second-brown">{t('refund.h10')}</p>
<p>{t('refund.p31')}</p>
<p>{t('refund.p32')}</p>
<p>{t('refund.p33')}</p>
<br />
<hr />
<br />
<p className="second-brown">Cambios a esta Política de Reembolso</p>
<p>Nos reservamos el derecho de actualizar esta Política de Reembolso en cualquier momento. Le recomendamos que revise periódicamente esta página para conocer los cambios.</p>
<p className="second-brown">{t('refund.h11')}</p>
<p>{t('refund.p34')}</p>
<br />
<hr />
<br />
<p className="second-brown">Contáctanos</p>
<p>Si tiene alguna pregunta o inquietud sobre esta Política de Reembolso, puede contactarnos a través de la siguiente información de contacto:</p>
<p className="second-brown">{t('refund.h12')}</p>
<p>{t('refund.p35')}</p>
<p>Error 404</p>
<p>ultimowow.com</p>
<p>info@ultimowow.com</p>
<br />
<p className="grey-info2">Fecha de actualización: 25-05-2023</p>
<p className="grey-info2">{t('refund.updated')}</p>
</div>
</div>
</div>
+11 -17
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -22,43 +22,37 @@ export default async function RenameCharacterPage({ params }: { params: Promise<
const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getRenamePrice()])
const t = await getTranslations('CharServiceB.rename')
return (
<PageShell title="Renombrar personaje">
<ServiceBox>
<br />
<p>
La herramienta <span>Renombrar personaje</span> te permite cambiar de nombre a un personaje.
</p>
<p>{t.rich('intro', { s: (c) => <span>{c}</span> })}</p>
<br />
<p>
Al usar el botón RENOMBRAR del personaje que hayas escogido, se enviará una petición de cambio de nombre de
dicho personaje.
</p>
<p>Cuando intentes entrar al reino con ese personaje, te pedirá que escojas un nuevo nombre.</p>
<p>{t('instructions')}</p>
<p>{t('newNameInfo')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('noteLegend')}</legend>
<div className="separate2">
<p>
Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez
realizada.
</p>
<p>{t('noteConfirm')}</p>
</div>
</fieldset>
<div className="centered">
<br />
<br />
<p>Requiere <span>{price}</span> <span className="yellow-info"></span> (SumUp)</p>
<p>{t.rich('requires', { price, s: (c) => <span>{c}</span>, y: (c) => <span className="yellow-info">{c}</span> })}</p>
</div>
<PaidServiceForm
characters={chars.map((c) => ({ name: c.name, classCss: c.classCss }))}
checkoutEndpoint="/api/character/rename/checkout"
payLabel="RENOMBRAR"
payLabel={t('payLabel')}
buttonClass="rename-button"
provider="sumup"
confirmText={`¿Estás seguro de renombrar al personaje seleccionado por ${price} € (SumUp)?`}
confirmText={t('confirm', { price })}
/>
</ServiceBox>
</PageShell>
+18 -15
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGuildMasterGuilds, GUILD_RENAME_PD } from '@/lib/guild'
@@ -14,6 +14,7 @@ export const metadata: Metadata = { title: 'Renombrar hermandad' }
export default async function RenameGuildPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Points')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -22,33 +23,29 @@ export default async function RenameGuildPage({ params }: { params: Promise<{ lo
const guilds = await getGuildMasterGuilds(session.accountId!)
return (
<PageShell title="Renombrar hermandad">
<PageShell title={t('renameGuild.title')}>
<ServiceBox>
<br />
<p>
La herramienta <span>Renombrar hermandad</span> te permite cambiar de nombre a una hermandad de la cual seas
Maestro de Hermandad.
{t.rich('renameGuild.intro1', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>A la hora de escoger un nuevo nombre, ten en cuenta:</p>
<p>- La longitud máxima es de 24 caracteres.</p>
<p>- Los caracteres permitidos son A-Za-z y el espacio.</p>
<p>{t('renameGuild.rules')}</p>
<p>{t('renameGuild.rule1')}</p>
<p>{t('renameGuild.rule2')}</p>
<br />
<p>
Al usar el botón RENOMBRAR HERMANDAD de la hermandad que hayas escogido, se cambiará su nombre al nuevo nombre
que hayas ingresado.
{t('renameGuild.intro2')}
</p>
<p>
El cambio de nombre de hermandad es instantáneo. Puede ser necesario que los miembros de la hermandad
conectados deban volver a conectar para finalmente ver el nuevo nombre.
{t('renameGuild.intro3')}
</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('renameGuild.note')}</legend>
<div className="separate2">
<p>
Por favor, asegúrate de haber elegido el nombre correcto ya que esta acción no es reversible una vez
realizada.
{t('renameGuild.noteText')}
</p>
</div>
</fieldset>
@@ -56,7 +53,13 @@ export default async function RenameGuildPage({ params }: { params: Promise<{ lo
<div className="centered">
<br />
<br />
<p>Requiere <span>{GUILD_RENAME_PD}</span> <span className="yellow-info">PD</span></p>
<p>
{t.rich('renameGuild.requires', {
pd: GUILD_RENAME_PD,
s: (c) => <span>{c}</span>,
y: (c) => <span className="yellow-info">{c}</span>,
})}
</p>
<br />
</div>
@@ -1,5 +1,6 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { NoteLegend } from '@/components/NoteLegend'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getRestorableCharacters } from '@/lib/restore-character'
@@ -14,6 +15,7 @@ export const metadata: Metadata = { title: 'Recuperar personaje' }
export default async function RestoreCharacterPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('CharService')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -22,26 +24,26 @@ export default async function RestoreCharacterPage({ params }: { params: Promise
const chars = await getRestorableCharacters(session.accountId!)
return (
<PageShell title="Recuperar personaje">
<PageShell title={t('restoreChar.pageTitle')}>
<ServiceBox>
<br />
<p>
La herramienta <span>Recuperar personaje</span> te permite restaurar un personaje que haya sido borrado.
{t.rich('restoreChar.intro', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>Sólo disponible para personajes que cumplan las siguientes dos condiciones:</p>
<p>- Nivel superior a 60.</p>
<p>- Borrado en un período menor a 30 días.</p>
<p>Para personajes <span className="death-knight">Caballeros de la Muerte:</span></p>
<p>- No es posible recuperar un personaje Caballero de la Muerte si ya hay otro activo en la cuenta.</p>
<p>{t('restoreChar.conditions')}</p>
<p>{t('restoreChar.cond1')}</p>
<p>{t('restoreChar.cond2')}</p>
<p>{t.rich('restoreChar.dkHeading', { dk: (c) => <span className="death-knight">{c}</span> })}</p>
<p>{t('restoreChar.dkCond')}</p>
<br />
<p>Cuando ingreses a la lista de personajes de tu cuenta, verás al personaje recuperado.</p>
<p>{t('restoreChar.afterRestore')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<NoteLegend />
<div className="separate2">
<p>Personajes borrados que no cumplan dichas condiciones ya no son recuperables.</p>
<p>Se puede repetir la acción en un mismo personaje cada 12 horas.</p>
<p>{t('restoreChar.noteConditions')}</p>
<p>{t('restoreChar.noteCooldown')}</p>
</div>
</fieldset>
+20 -13
View File
@@ -1,5 +1,6 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { NoteLegend } from '@/components/NoteLegend'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -15,6 +16,7 @@ export const metadata: Metadata = { title: 'Recuperar ítems' }
export default async function RestoreItemsPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('CharService')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -23,34 +25,39 @@ export default async function RestoreItemsPage({ params }: { params: Promise<{ l
const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getRestoreItemPrice()])
return (
<PageShell title="Recuperar ítems">
<PageShell title={t('restoreItems.pageTitle')}>
<ServiceBox>
<br />
<p>
La herramienta <span>Recuperar ítems</span> te permite recuperar ítems que hayan sido borrados.
{t.rich('restoreItems.intro', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>Sólo disponible para ítems que cumplan las siguientes condiciones:</p>
<p>{t('restoreItems.conditions')}</p>
<p>
- De calidad: <span className="q3">Raro</span> | <span className="q4">Épico</span> |{' '}
<span className="q5">Legendaria</span> | <span className="q6">Artefacto</span> | <span className="q6">Reliquia</span>.
{t.rich('restoreItems.quality', {
q3: (c) => <span className="q3">{c}</span>,
q4: (c) => <span className="q4">{c}</span>,
q5: (c) => <span className="q5">{c}</span>,
q6: (c) => <span className="q6">{c}</span>,
q6b: (c) => <span className="q6">{c}</span>,
})}
</p>
<p>- Equipable.</p>
<p>- Se liga al recogerlo.</p>
<p>- Borrado en un período menor a 7 días.</p>
<p>{t('restoreItems.condEquip')}</p>
<p>{t('restoreItems.condBound')}</p>
<p>{t('restoreItems.condDeleted')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<NoteLegend />
<div className="separate2">
<p>Ítems borrados que no cumplan dichas condiciones ya no son recuperables.</p>
<p>Se pueden consultar ítems borrados en un mismo personaje cada 8 horas.</p>
<p>{t('restoreItems.noteConditions')}</p>
<p>{t('restoreItems.noteCooldown')}</p>
</div>
</fieldset>
<div className="centered">
<br />
<br />
<p>Cada ítem requiere <span>{price}</span> <span className="yellow-info"></span> (SumUp)</p>
<p>{t.rich('restoreItems.requires', { price, s: (c) => <span>{c}</span>, e: (c) => <span className="yellow-info">{c}</span> })}</p>
<br />
<RestoreItemsForm characters={chars.map((c) => ({ name: c.name, classCss: c.classCss }))} price={price} />
</div>
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect, Link } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { is2faEnabled } from '@/lib/two-factor'
@@ -15,6 +15,7 @@ const IMG = '/nw-themes/nw-ryu/nw-images'
export default async function Security2faLoginPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Misc')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -23,12 +24,12 @@ export default async function Security2faLoginPage({ params }: { params: Promise
const enabled = session.accountId ? await is2faEnabled(session.accountId) : false
return (
<PageShell title="Verificación en 2 pasos">
<PageShell title={t('twofaLogin.pageTitle')}>
<div className="box-content">
<div className="title-box-content">
<h2>Información</h2>
<Link className="back-to-account" href="/my-account">Regresar a mi cuenta</Link>
<Link className="back-to-account-responsive" href="/my-account">Regresar</Link>
<h2>{t('twofaLogin.info')}</h2>
<Link className="back-to-account" href="/my-account">{t('twofaLogin.backToAccount')}</Link>
<Link className="back-to-account-responsive" href="/my-account">{t('twofaLogin.back')}</Link>
</div>
<div className="body-box-content justified">
<br />
@@ -36,30 +37,30 @@ export default async function Security2faLoginPage({ params }: { params: Promise
<img
className="twofa-login-preview"
src={`${IMG}/nw-general/nw-authenticator-preview.png`}
alt="Vista previa autenticador"
alt={t('twofaLogin.previewAlt')}
/>
<div className="twofa-login-info-text">
<p><span>2FA</span> son las siglas de &quot;Two Factor Authentication&quot;, o verificación en dos pasos.</p>
<p>{t.rich('twofaLogin.p1', { s: (c) => <span>{c}</span> })}</p>
<br />
<p>Es un método que añade una capa adicional de seguridad al proceso de inicio de sesión.</p>
<p>En lugar de simplemente ingresar tu nombre de usuario y contraseña, 2FA requiere un segundo paso de verificación, aumentando así la protección de tu cuenta.</p>
<p>{t('twofaLogin.p2')}</p>
<p>{t('twofaLogin.p3')}</p>
<br />
<p>Una vez activado, luego de ingresar usuario y contraseña, aparecerá una ventana (Imagen derecha) que te pedirá el código de autenticación generado por la aplicación.</p>
<p>{t('twofaLogin.p4')}</p>
<br />
<p><span>Códigos de respaldo</span></p>
<p>Al activar 2FA, se te proporcionarán códigos de respaldo únicos.</p>
<p>Estos códigos son útiles en caso de que pierdas acceso a tu aplicación de autenticación.</p>
<p>Cada código de respaldo puede usarse una sola vez.</p>
<p>Guárdalos en un lugar seguro, ya que te permitirán acceder a tu cuenta en el sitio web si no puedes usar la aplicación de autenticación.</p>
<p><span>{t('twofaLogin.backupCodes')}</span></p>
<p>{t('twofaLogin.p5')}</p>
<p>{t('twofaLogin.p6')}</p>
<p>{t('twofaLogin.p7')}</p>
<p>{t('twofaLogin.p8')}</p>
<br />
<p>Si aún no tienes Token de seguridad, puedes solicitarlo <Link href="/security-token" target="_blank">aquí</Link>.</p>
<p>{t.rich('twofaLogin.requestToken', { link: (c) => <Link href="/security-token" target="_blank">{c}</Link> })}</p>
<br />
</div>
</div>
<fieldset>
<legend>NOTA</legend>
<legend>{t('twofaLogin.note')}</legend>
<div className="separate2">
<p>Utiliza una aplicación de autenticación para iniciar sesión en el juego una vez que lo tengas activado.</p>
<p>{t('twofaLogin.noteText')}</p>
</div>
</fieldset>
+25 -26
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { setRequestLocale, getTranslations } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getSecurityHistory } from '@/lib/security-history'
@@ -35,6 +35,7 @@ function UserCell({ label, bnet }: { label: string; bnet: string }) {
export default async function SecurityHistoryPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('History')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -45,39 +46,37 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
const { activity, realm, web } = await getSecurityHistory(session.accountId!, displayUser, bnetEmail)
return (
<PageShell title="Historial de seguridad">
<PageShell title={t('security.pageTitle')}>
<ServiceBox>
<br />
<p>Aquí verás información de actividad y conexiones de tu cuenta.</p>
<p>Cada tabla tiene una explicación detallada de la información provista.</p>
<p>{t('security.intro1')}</p>
<p>{t('security.intro2')}</p>
<p>
Si desconoces algún tipo de actividad te recomendamos cambiar la contraseña de tu cuenta y del correo ligado a
la misma.
{t('security.intro3')}
</p>
<br />
<p>
<span>Historial de actividad</span>
<span>{t('security.activityTitle')}</span>
</p>
<p>Muestra actividad de seguridad de la cuenta, como por ejemplo cambios de contraseña.</p>
<p>{t('security.activityDesc')}</p>
<br />
<p>
<span>Historial de conexiones (Reino)</span>
<span>{t('security.realmTitle')}</span>
</p>
<p>
Muestra un historial de conexiones al reino <u>sólo cuando la IP ha cambiado</u>. No es un historial de cada
conexión.
{t.rich('security.realmDesc', { u: (c) => <u>{c}</u> })}
</p>
<br />
<p>
<span>Historial de conexiones (Web)</span>
<span>{t('security.webTitle')}</span>
</p>
<p>Muestra un historial de cada conexión al sitio web.</p>
<p>{t('security.webDesc')}</p>
</ServiceBox>
{/* Historial de actividad */}
<div className="box-content">
<div className="title-box-content">
<h2>Historial de actividad</h2>
<h2>{t('security.activityTitle')}</h2>
</div>
<div className="body-box-content centered">
{activity.length === 0 ? (
@@ -85,7 +84,7 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
<tbody>
<tr>
<td>
<span>No hay actividad</span>
<span>{t('security.noActivity')}</span>
</td>
</tr>
</tbody>
@@ -94,10 +93,10 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
<table className="max-center-table-aligned2">
<tbody>
<tr>
<th>Usuario</th>
<th>Acción</th>
<th>{t('security.thUser')}</th>
<th>{t('security.thAction')}</th>
<th>IP</th>
<th>Fecha</th>
<th>{t('security.thDate')}</th>
</tr>
{activity.map((a, i) => (
<tr key={i}>
@@ -122,7 +121,7 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
{/* Historial de conexiones (Reino) */}
<div className="box-content">
<div className="title-box-content">
<h2>Historial de conexiones (Reino)</h2>
<h2>{t('security.realmTitle')}</h2>
</div>
<div className="body-box-content centered">
{realm.length === 0 ? (
@@ -130,7 +129,7 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
<tbody>
<tr>
<td>
<span>No hay conexiones</span>
<span>{t('security.noConnections')}</span>
</td>
</tr>
</tbody>
@@ -140,7 +139,7 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
<tbody>
<tr>
<th>IP</th>
<th>Fecha</th>
<th>{t('security.thDate')}</th>
</tr>
{realm.map((c, i) => (
<tr key={i}>
@@ -161,7 +160,7 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
{/* Historial de conexiones (Web) */}
<div className="box-content">
<div className="title-box-content">
<h2>Historial de conexiones (Web)</h2>
<h2>{t('security.webTitle')}</h2>
</div>
<div className="body-box-content centered">
{web.length === 0 ? (
@@ -169,7 +168,7 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
<tbody>
<tr>
<td>
<span>No hay conexiones</span>
<span>{t('security.noConnections')}</span>
</td>
</tr>
</tbody>
@@ -178,10 +177,10 @@ export default async function SecurityHistoryPage({ params }: { params: Promise<
<table className="max-center-table-aligned">
<tbody>
<tr>
<th>Usuario</th>
<th>Estado</th>
<th>{t('security.thUser')}</th>
<th>{t('security.thStatus')}</th>
<th>IP</th>
<th>Fecha</th>
<th>{t('security.thDate')}</th>
</tr>
{web.map((c, i) => (
<tr key={i}>
@@ -1,4 +1,5 @@
import type { RowDataPacket } from 'mysql2'
import { NoteLegend } from '@/components/NoteLegend'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
@@ -41,7 +42,7 @@ export default async function SecurityTokenPage({ params }: { params: Promise<{
<p><span className="red-info2">{t('warning')}</span></p>
<br />
<fieldset>
<legend>NOTA</legend>
<NoteLegend />
<div className="separate2">
<p>{t('note')}</p>
</div>
+11 -16
View File
@@ -1,5 +1,6 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { NoteLegend } from '@/components/NoteLegend'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect, Link } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -15,6 +16,7 @@ export const metadata: Metadata = { title: 'Enviar regalo' }
export default async function SendGiftPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('CharService')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -24,38 +26,31 @@ export default async function SendGiftPage({ params }: { params: Promise<{ local
const currency = process.env.SUMUP_CURRENCY === 'EUR' ? '€' : process.env.SUMUP_CURRENCY || '€'
return (
<PageShell title="Enviar regalo">
<PageShell title={t('sendGift.pageTitle')}>
<ServiceBox>
<br />
<p>
La herramienta <span>Enviar regalo</span> te permite enviar regalos a tus amigos.
{t.rich('sendGift.intro', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>
Al elegir desde qué personaje se enviará el regalo y el personaje que lo recibirá, podrás ver la lista de
objetos disponibles para enviar como regalo.
{t('sendGift.explain1')}
</p>
<p>Los objetos disponibles son los mismos de la tienda y puedes enviar varios objetos a la vez.</p>
<p>{t('sendGift.explain2')}</p>
<br />
<p>
Al usar el botón ENVIAR REGALO se enviará un correo al personaje destino con los ítems seleccionados,
indicando quién lo ha regalado. El pago se realiza mediante <span>SumUp</span>.
{t.rich('sendGift.explain3', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>
Si aún no tienes Token de seguridad, puedes solicitarlo{' '}
<Link href="/security-token" target="_blank">
aquí
</Link>
.
{t.rich('sendGift.securityTokenHint', { link: (c) => <Link href="/security-token" target="_blank">{c}</Link> })}
</p>
<br />
<fieldset>
<legend>NOTA</legend>
<NoteLegend />
<div className="separate2">
<p>
Por favor, asegúrate de haber escrito el nombre del personaje correcto ya que esta acción no es
reversible una vez realizada.
{t('sendGift.noteIrreversible')}
</p>
</div>
</fieldset>
@@ -1,112 +1,114 @@
import type { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
export const metadata: Metadata = {
title: 'Términos y condiciones',
}
export default function TermsAndConditionsPage() {
export default async function TermsAndConditionsPage() {
const t = await getTranslations('Legal')
return (
<div className="main-page">
<div className="middle-content">
<div className="body-content">
<div className="title-content">
<h1>Términos y condiciones</h1>
<h1>{t('terms.title')}</h1>
</div>
<div className="box-content">
<div className="body-box-content">
<span className="second-brown">IMPORTANTE</span>
<p>Por favor, tómese un momento para leer los términos y condiciones detallados debajo. Si está de acuerdo con ellos, entonces podrá ingresar en nuestro sitio.</p>
<span className="second-brown">{t('terms.h1')}</span>
<p>{t('terms.p1')}</p>
<br />
<hr />
<br />
<span className="second-brown">ACEPTACIÓN DE LOS TÉRMINOS DE USO Y CONDICIONES</span>
<p>UltimoWoW es un proyecto sin ánimo de lucro desarrollado con la finalidad de simular versiones desactualizadas del servidor con fines únicamente educativos. Los Servicios ofrecidos por UltimoWoW no apoyan ni proveerán modificación de ningún tipo para con el servidor o sus ficheros, dicho esto, el usuario al hacer uso de este sitio o cualquier servicio perteneciente o derivado de la Administración, acepta hacerse responsable de seguir y cumplir con el contrato de licencia para usuarios finales (EULA) del servidor.</p>
<span className="second-brown">{t('terms.h2')}</span>
<p>{t('terms.p2')}</p>
<br />
<p>UltimoWoW no provee ni se responsabiliza por la distribución de ningún cliente del servidor o fuente externa que lo contenga. Los enlaces de descarga para el cliente ofrecidos por este sitio o cualquier medio bajo su responsabilidad, apuntarán siempre a dichas fuentes externas, las cuales, a la fecha no pertenecen ni poseen relación alguna con esta administración. No haciéndose UltimoWoW de esta manera, y bajo ningún concepto, responsable sobre aspecto alguno sobre dicha fuente, dígase: calidad, rendimiento, garantías o cualquiera fuese o no, característica determinante a su fin.</p>
<p>{t('terms.p3')}</p>
<br />
<p>Al acceder, navegar o usar este sitio de Internet y en adelante (el Sitio), propiedad de la administración UltimoWoW en adelante (la Administración), el usuario admite haber leído y entendido los presentes términos y condiciones de uso, en adelante (Términos y Condiciones) y está de acuerdo en acatar los mismos y cumplir con todas las normas, leyes y reglamentos aplicables que hagan parte de la legislación vigente en su lugar de residencia, o a las que se sujete indistintamente. Además, cuando el usuario utilice cualquier servicio suministrado o referenciado en el Sitio, (siempre y cuando éste resida bajo responsabilidad o propiedad de la Administración) estará sujeto a las reglas, guías, políticas, términos y condiciones aplicables a dichos servicios.</p>
<p>{t('terms.p4')}</p>
<br />
<p>Este Sitio es controlado y desarrollado por la Administración, localizable a través de sus plataformas de Discord.</p>
<p>{t('terms.p5')}</p>
<br />
<p>La Administración no es responsable de que el material en este Sitio sea apropiado o esté disponible para su uso en otros lugares, estando prohibido su acceso desde territorios donde su contenido sea ilegal. Aquellos que decidan acceder a este Sitio desde otros lugares lo harán bajo su propia iniciativa, y es su responsabilidad el sujetarse a las leyes locales que sean aplicables.</p>
<p>{t('terms.p6')}</p>
<br />
<p>Estos Términos y Condiciones están sujetos a cambios a discreción, sin previo aviso y en cualquier momento, bajo la sola voluntad de UltimoWoW, y partiendo de la fecha de publicación de la modificación de los mismos en este Sitio, todas las operaciones celebradas entre la Administración y el usuario se regirán por el documento modificado.</p>
<p>{t('terms.p7')}</p>
<br />
<hr />
<br />
<span className="second-brown">NUESTRO SERVICIO: USO, DISTRIBUCIÓN Y DESCARGO DE RESPONSABILIDADES</span>
<p>De conformidad con las legislaciones bajo las cuales la Administración se acate, el material contenido en este Sitio o cualquier otro que se encontrase bajo su responsabilidad o tutela y en adelante, (los Servicios o el Servicio), incluyendo sin limitación, toda imagen, links, logotipo, diseño, insignia, marca, fotografía, sonido, texto, mensaje, herramienta, software, tecnología, producto, archivo, información, dato, demostración, muestra, material promocional, obra audiovisual u obra multimedia, y cualquiera sea otro elemento o forma de expresión (de forma colectiva y en adelante, (los &quot;Materiales&quot;), son suministrados según nuestro leal saber y entender, sin embargo, no es posible descartar errores de contenido o del material, razón por la cual la Administración no asume responsabilidad por la calidad, veracidad o exactitud, de la información publicada ni por las omisiones, errores o discrepancias que pudiesen encontrarse en la información publicada o servicio brindado. </p>
<span className="second-brown">{t('terms.h3')}</span>
<p>{t('terms.p8')}</p>
<br />
<p>Respecto a la información publicada en los Servicios, la Administración no confiere garantías de ningún tipo, expresas ni implícitas, excluyendo, más no limitado a:</p>
<p>- Garantías de comerciabilidad o adecuación para un propósito particular.</p>
<p>- Estabilidad ininterrumpida del Servicio.</p>
<p>- Existencia de errores o virus, así como sobre su seguridad.</p>
<p>- Exactitud, confiabilidad, calificación o certificación para su descarga.</p>
<p>{t('terms.p9')}</p>
<p>{t('terms.p10')}</p>
<p>{t('terms.p11')}</p>
<p>{t('terms.p12')}</p>
<p>{t('terms.p13')}</p>
<br />
<p>La Administración no se hace responsable sobre el uso que el usuario haga de los contenidos incluidos en los Servicios, ni de las decisiones que tome respecto a éstos. La información respecto a los productos y servicios de UltimoWoW, incluida en sus Servicios es brindada con fines edcuativos no comerciales y no constituye oferta de venta para sus usuarios.</p>
<p>{t('terms.p14')}</p>
<br />
<p>UltimoWoW se reserva el derecho de a discreción:</p>
<p>- Corregir cualquier error, omisión o inexactitud en los datos brindados.</p>
<p>- Cambiar o actualizar, descontinuar o eliminar la información contenida en los Servicios en cualquier momento y sin previo aviso.</p>
<p>- Así como también se exime de responsabilidad sobre lo que refiere a la puntualidad, falta de almacenamiento, inexactitud o entrega incorrecta de cualquier información o dato de los que normalmente proporcione, ya sea temporal o permanentemente.</p>
<p>{t('terms.p15')}</p>
<p>{t('terms.p16')}</p>
<p>{t('terms.p17')}</p>
<p>{t('terms.p18')}</p>
<br />
<hr />
<br />
<span className="second-brown">RESPONSABILIDADES Y OBLIGACIONES DE REGISTRO Y CONTRASEÑAS</span>
<p>El usufructo de las principales funciones fundamentales y objetivas del Servicio estará siempre determinado y sujeto a la existencia de un registro personal e intransferible (dígase cuenta de usuario). La Administración le exhorta a proporcionar información veraz y comprobable según fuese la misma solicitada. Más allá de un carácter intrusivo, dicha información se haría utilizar estricta y discretamente en aras de posibilitar a futuro, una resolución por parte de la Administración, de alguna situación extraordinaria que surgiese por descuidos de parte del usuario, intentando así dictar la misma en la forma menos perjudicial posible para con este. </p>
<span className="second-brown">{t('terms.h4')}</span>
<p>{t('terms.p19')}</p>
<br />
<p>Pertenece completa y estrictamente, y recae sobre el usuario la responsabilidad de mantener confidenciales y de forma segura las contraseñas establecidas para con el Servicio. Así como de las acciones a través del uso de su registro que pudiesen estar estas bajo autorización o no indistintamente por parte del usuario. Queda así también bajo responsabilidad del usuario, la notificación inmediata a la Administración si éste notase alguna actividad sospechosa, uso no autorizado o registro no concebido en el que figurase su propia cuenta como objeto de manipulación, o violación.</p>
<p>{t('terms.p20')}</p>
<br />
<p>Queda terminantemente prohibido el intercambio, comercio, compra-venta y cualquier actividad similar de cualquier forma y bajo cualquier concepto donde figurase e involucrase el conocimiento por parte de cualquiera persona ajena al usuario; de sus credenciales de acceso al Servicio. Violar dichas prohibiciones podría hacer incurrir al usuario en una falta grave, la cual podría sancionarse con penalizaciones de magnitud determinada por la Administración a su juicio y a discreción, la cual en ningún caso quedaría exenta de dicha penalización mediante alegato de uso por parte de terceros y en general bajo ninguna justificación.</p>
<p>{t('terms.p21')}</p>
<br />
<p>Los personajes del reino que hubiesen sido borrados y alcanzados estos, el límite máximo de tiempo (on-hold) para recuperación, establecido por la Administración, desaparecerán permanentemente, claudicando con ellos la posibilidad de restauración o asistencia por parte de la Administración en estos casos, independientemente y descartando por completo la voluntad del usuario para con tal acción en dicho momento.</p>
<p>{t('terms.p22')}</p>
<br />
<hr />
<br />
<span className="second-brown">HACER TRAMPA</span>
<p>Se entiende como trampa cualquier acto fuera y dentro del servidor, que intente alterar o de hecho altere e interfiera con las reglas o con el comportamiento normal del servidor. Así como intentar o de hecho aventajarse sobre otros jugadores. Entre ello, a título meramente enunciativo y sin ánimo limitativo, cualquiera de los siguientes comportamientos, ya sea en su propio nombre o por cuenta de terceros será considerado como tal:</p>
<p>- Acceder a los Servicios de manera no autorizada (incluyendo el uso de software modificado o malicioso).</p>
<p>- Utilizar varias cuentas a la vez en un mismo reino de una forma en la que controlar varios personajes al unísono y sobre el mismo reino, suponga una desventaja de cualquier índole para otros usuarios con respecto infractor.</p>
<p>- Utilizar cualquier técnica o software para alterar o manipular las variables preestablecidas por el servidor y la Administración.</p>
<span className="second-brown">{t('terms.h5')}</span>
<p>{t('terms.p23')}</p>
<p>{t('terms.p24')}</p>
<p>{t('terms.p25')}</p>
<p>{t('terms.p26')}</p>
<br />
<p>Se prohíbe terminantemente hacer trampas y la Administración se reserva el derecho de denegar a cualquier usuario el acceso al Servicio, siempre y cuando hubiere incurrido o fuese objeto de una falta de este tipo ya fuese directa e indirectamente.</p>
<p>{t('terms.p27')}</p>
<br />
<hr />
<br />
<span className="second-brown">COMPRAS, RECLAMACIONES, REEMBOLSOS Y GARANTÍAS</span>
<p>UltimoWoW no representa ni constituye entidad remesadora ni de servicios financieros de ninguna índole y en consecuencia no posee control o responsabilidad alguna sobre el origen de aquellos fondos utilizados para realizar compras hacia su comunidad. Así como tampoco se responsabiliza por los riesgos que el usuario tomase bajo su propia voluntad, a la hora de ejecutar operaciones financieras de cualquier tipo.</p>
<span className="second-brown">{t('terms.h6')}</span>
<p>{t('terms.p28')}</p>
<br />
<p>Las compras: serán consideradas siempre de fe voluntaria y en ningún caso, obligación de ningún tipo para ningún usuario. Nótese además que quedan terminantemente prohibidos las compras por parte de personas naturales menores de 18 años o dígase en cualquier caso aquellas personas que no alcanzaran aún al amparo de las entidades legales locales y pertinentes que le competan, su mayoría de edad.</p>
<p>{t('terms.p29')}</p>
<br />
<p>El Servicio: en cualquier caso, no requiere ningún tipo de compra para un uso y disfrute total y a plenitud. Dicho esto, el usuario contará con un canal de comunicación oficial vía email contacto@ultimowow.com con el objetivo de notificar a la Administración de cualquier error cometido por cualquiera de las partes, en aras de emitir y hacer efectiva justa resolución</p>
<p>{t('terms.p30')}</p>
<br />
<p>La Administración: garantiza y se compromete en brindar al comprador toda información que requiriese sobre las formas o métodos de compra vigentes para con la comunidad. Así como aclarar cualquier duda que pudiese surgir sobre los pasos o procesos establecidos de las mismas, siempre y cuando dicha información resida dentro de los límites que competan al comprador y posea un propósito únicamente informativo.</p>
<p>{t('terms.p31')}</p>
<br />
<p>Tomado en cuenta lo anterior, el comprador cuenta con garantías de reembolso monetario y de servicios, atendiendo a aspectos y condiciones expuestos a continuación.</p>
<p className="second-brown">Monetarios:</p>
<p>Tomando en cuenta el carácter que posee cualquier transacción monetaria hacia las arcas de la comunidad, la Administración se compromete a reembolsar (libre de costos por transacción, a consideración de la misma), de forma total o parcial, en definitiva, colegiada conjuntamente entre ambas partes, aquellas compras donde no intencionalmente, el usuario quedase bajo la inconveniencia a la hora de realizar la misma, de errores por su parte en la cuantía del monto total a transferir. Siendo dicho error en todos los casos veraz y de comprobable naturaleza a consideración de la administración.</p>
<p>{t('terms.p32')}</p>
<p className="second-brown">{t('terms.h7')}</p>
<p>{t('terms.p33')}</p>
<br />
<p>Tomando en cuenta que los servicios adquiridos se reciben de forma inmediata y no constituyen, determinan, pactan ni modifican plazo alguno para su uso. Así como tampoco para el usufructo del servicio y sus funcionalidades los cuales, recalcado sea de paso, se consideran completamente gratuitos, de carácter educativo y carecen de plazos de admisión y vencimiento.</p>
<p>{t('terms.p34')}</p>
<br />
<p>La Administración no garantiza ni proporcionará en ningún caso ni bajo ningún concepto o circunstancia, reembolso alguno sobre compras o cualquier bien en general transferido a la comunidad. Nótense y háganse aclaratorios lo siguientes casos:</p>
<p>- A usuarios cuyas cuentas hayan sido sancionadas temporal o permanentemente por incurrir en alguna falta expuesta en cualquiera de sus Términos y Condiciones.</p>
<p>- A usuarios que hubiesen tomado la decisión de no continuar (parcial o temporalmente) haciendo uso de cualquiera de los servicios prestados por La Administración.</p>
<p>{t('terms.p35')}</p>
<p>{t('terms.p36')}</p>
<p>{t('terms.p37')}</p>
<br />
<p>En ninguno de estos casos la Administración presentará completa ni parcialmente traza alguna de ninguno de sus registros monetarios o de cualquier tipo para fines resolutorios.</p>
<p>{t('terms.p38')}</p>
<br />
<p>Igualmente, el usuario dispone de las respectivas vías y canales, dígase dentro de los foros del Servicio, o servidor de Discord para presentar una apelación formal ante un administrador y ser así evaluado el caso para posterior resolución. La Administración se reserva el derecho de evaluar más no garantizar y a discreción, reembolsos en aquellos casos donde después de estudiado y conciliado, fuese considerado por la misma, justo y extraordinario.</p>
<p>{t('terms.p39')}</p>
<br />
<p className="second-brown">De servicios:</p>
<p>La Administración le garantiza la integridad y funcionalidad de todos los artículos comprados en su tienda. En todo caso, ésta le proveerá un término de 48 horas posterior a su compra para solicitar reemplazos por artículos o servicios de igual valor, o también y a solicitud, la devolución íntegra de los servicios utilizados en aquellos casos donde se encontrarse dicho artículo o servicio roto o inutilizable, o hubiere sido comprado o elegido por error, todo esto siempre y después de efectuado un análisis pertinente por parte de un administrador, y de demostradas pruebas auténticas y concluyentes por parte del usuario a fines con el caso.</p>
<p className="second-brown">{t('terms.h8')}</p>
<p>{t('terms.p40')}</p>
<br />
<p>Así como también la Administración garantizará la restauración del servicio por problemas técnicos y puntuales, entre el Servicio y las plataformas de pago siempre ajenos al comprador, no fuesen entregados dichos servicios de forma automática e inmediata a nombre del mismo. </p>
<p>{t('terms.p41')}</p>
<br />
<hr />
<br />
<span className="second-brown">POLÍTICA DE PRIVACIDAD</span>
<p>El Servicio y la Administración recopilan información referente al usuario tales como dirección de correo electrónico, nombre, contraseña, idioma y zona horaria, con el fin de brindarle una mejor optimización cada vez a sus servicios, utilizando esta información para acomodar las funcionalidades a un mejor rendimiento a fin con el usuario y sus requerimientos. Dicha información también podrá ser usada para mantener al usuario mediante canales de comunicaciones independientes y no referentes al Servicio, informando sobre noticias importantes, actualizaciones, problemas técnicos o cualquier otra información de tal índole que considerase la Administración relevante a los intereses del usuario. </p>
<span className="second-brown">{t('terms.h9')}</span>
<p>{t('terms.p42')}</p>
<br />
<p>La Administración y el Servicio se comprometen a salvaguardar la confidencialidad sobre tales datos, así como a garantizar la NO divulgación de estos, exceptuando los casos donde las leyes vigentes que rigiesen sobre la Administración lo consideraran necesario y mandatorio por motivos no referentes a UltimoWoW. </p>
<p>{t('terms.p43')}</p>
<br />
<p className="grey-info2">Error 404</p>
<p className="grey-info2">info@ultimowow.com</p>
+35 -31
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect, Link } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -14,6 +14,7 @@ export const metadata: Metadata = { title: 'Comercio de PD' }
export default async function TradePointsPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Points')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -22,59 +23,62 @@ export default async function TradePointsPage({ params }: { params: Promise<{ lo
const chars = await getGameCharacters(session.accountId!)
return (
<PageShell title="Comercio de PD">
<PageShell title={t('trade.title')}>
<ServiceBox>
<br />
<p>
La herramienta <span>Comercio de PD</span> permite comprar o vender PD a cambio de oro de una forma segura.
{t.rich('trade.intro1', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>El sistema permite crear códigos de 1 solo uso que valdrán X cantidad de PD a cambio de X cantidad de oro.</p>
<p>{t('trade.intro2')}</p>
<p>
Los vendedores disponen de la sección VENTA para crear el código y los compradores disponen de la sección
COMPRA para canjear dicho código.
{t('trade.intro3')}
</p>
<br />
<div className="trade-information">
<h2>Venta</h2>
<p>1) Decide cuánto valdrá el código en PD.</p>
<p>2) Decide por cuánto oro se podrá canjear.</p>
<p>3) Escoge a qué personaje de la cuenta se enviará el oro cuando el código sea canjeado.</p>
<p>4) Completa los datos con la contraseña y Token de Seguridad.</p>
<p>5) Usa el botón CREAR CÓDIGO.</p>
<p>6) Un recuadro aparecerá con los datos del Código creado con el ID que se debe usar para enviar a otro jugador.</p>
<h2>{t('trade.sellTitle')}</h2>
<p>{t('trade.sell1')}</p>
<p>{t('trade.sell2')}</p>
<p>{t('trade.sell3')}</p>
<p>{t('trade.sell4')}</p>
<p>{t('trade.sell5')}</p>
<p>{t('trade.sell6')}</p>
<br />
</div>
<div className="trade-information">
<h2>Compra</h2>
<p>1) Ingresa ID del código que te ha dado otro jugador.</p>
<p>2) Chequea el valor en PD y costo en oro con el botón CHEQUEAR CÓDIGO.</p>
<p>3) Escoge de qué personaje de la cuenta se descontará el oro cuando el código sea canjeado.</p>
<p>4) Completa los datos con la contraseña y Token de Seguridad.</p>
<p>5) Usa el botón CANJEAR CÓDIGO.</p>
<p>6) Un recuadro aparecerá con los datos del Código canjeado, PD recibidos y oro retirado del personaje seleccionado.</p>
<h2>{t('trade.buyTitle')}</h2>
<p>{t('trade.buy1')}</p>
<p>{t('trade.buy2')}</p>
<p>{t('trade.buy3')}</p>
<p>{t('trade.buy4')}</p>
<p>{t('trade.buy5')}</p>
<p>{t('trade.buy6')}</p>
</div>
<br />
<br />
<p>Cada código tiene una duración de 1 hora.</p>
<p>Una vez canjeado el código:</p>
<p>- Los PD serán retirados de la cuenta del creador del código y enviados a quien lo haya canjeado.</p>
<p>- El oro será retirado y enviado por correo al creador.</p>
<p>- La transacción quedará registrada en el Historial de PD y PV.</p>
<p>{t('trade.duration')}</p>
<p>{t('trade.onceRedeemed')}</p>
<p>{t('trade.redeemed1')}</p>
<p>{t('trade.redeemed2')}</p>
<p>{t('trade.redeemed3')}</p>
<br />
<p>La cuenta de quien canjee el código será bloqueada por 5s como medida de seguridad si el trámite es exitoso.</p>
<p>{t('trade.lockNote')}</p>
<br />
<p>
Si aún no tienes Token de seguridad, puedes solicitarlo{' '}
<Link href="/security-token" target="_blank">aquí</Link>.
{t.rich('trade.tokenHelp', {
link: (c) => (
<Link href="/security-token" target="_blank">
{c}
</Link>
),
})}
</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('trade.note')}</legend>
<div className="separate2">
<p>
Por favor, asegúrate de haber escrito todos los datos correctos ya que esta acción no es reversible una
vez realizada.
{t('trade.noteText')}
</p>
</div>
</fieldset>
+20 -19
View File
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { setRequestLocale, getTranslations } from 'next-intl/server'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getPaymentTransactions, type PaymentTx } from '@/lib/trans-history'
@@ -21,7 +21,7 @@ function StatusBadge({ status }: { status: PaymentTx['status'] }) {
}
/** Caja de una pasarela con su tabla de transacciones (o "No hay movimientos"). */
function PlatformBox({
async function PlatformBox({
name,
logo,
note,
@@ -32,6 +32,7 @@ function PlatformBox({
note: string
txs: PaymentTx[]
}) {
const t = await getTranslations('History')
return (
<div className="box-content">
<div className="title-box-content">
@@ -41,12 +42,11 @@ function PlatformBox({
</div>
<div className="body-box-content">
<p>
Recibirás los PD correspondientes a cada transacción sólo cuando esté aprobada y el estado sea{' '}
<span className="green-info2">PAID</span> (Pagado).
{t.rich('trans.paidNote', { s: (c) => <span className="green-info2">{c}</span> })}
</p>
<br />
<p>
<span className="red-info2">Importante:</span> {note}
<span className="red-info2">{t('trans.important')}</span> {note}
</p>
<br />
{txs.length === 0 ? (
@@ -54,7 +54,7 @@ function PlatformBox({
<tbody>
<tr>
<td>
<span>No hay movimientos</span>
<span>{t('trans.noMovements')}</span>
</td>
</tr>
</tbody>
@@ -63,11 +63,11 @@ function PlatformBox({
<table className="max-center-table-aligned">
<tbody>
<tr>
<th>ID de transacción</th>
<th>Estado</th>
<th className="responsive-td">Concepto</th>
<th>Fecha creación</th>
<th>Cantidad</th>
<th>{t('trans.thTxId')}</th>
<th>{t('trans.thStatus')}</th>
<th className="responsive-td">{t('trans.thConcept')}</th>
<th>{t('trans.thCreatedAt')}</th>
<th>{t('trans.thAmount')}</th>
</tr>
{txs.map((t) => (
<tr key={t.id}>
@@ -101,6 +101,7 @@ const LOGO_BASE = '/nw-themes/nw-ryu/nw-images/nw-logos'
export default async function TransHistoryPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('History')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -111,23 +112,23 @@ export default async function TransHistoryPage({ params }: { params: Promise<{ l
const sumup = txs.filter((t) => t.platform === 'SumUp')
return (
<PageShell title="Historial de transacciones">
<PageShell title={t('trans.pageTitle')}>
<ServiceBox>
<br />
<p>Aquí verás la información que recibimos de nuestras diferentes plataformas de pago automáticas.</p>
<p>{t('trans.intro1')}</p>
<br />
<p>¿Has tenido algún problema con alguna transacción?</p>
<p>{t('trans.intro2')}</p>
<p>
Si has tenido un problema con una de las transacciones o no has recibido tus PD, puedes contactarnos en
nuestro Discord o a través del correo{' '}
<a href="mailto:contacto@ultimowow.com">contacto@ultimowow.com</a>.
{t.rich('trans.intro3', {
mail: (c) => <a href="mailto:contacto@ultimowow.com">{c}</a>,
})}
</p>
</ServiceBox>
<br />
<PlatformBox
name="Stripe"
logo={`${LOGO_BASE}/stripe-p-logo.svg`}
note="Los pagos con tarjeta se aprueban normalmente en unos segundos."
note={t('trans.noteStripe')}
txs={stripe}
/>
<br />
@@ -136,7 +137,7 @@ export default async function TransHistoryPage({ params }: { params: Promise<{ l
<PlatformBox
name="SumUp"
logo={`${LOGO_BASE}/sumup-p-logo.svg`}
note="SumUp puede tardar unos minutos en confirmar el pago tras completar el checkout."
note={t('trans.noteSumup')}
txs={sumup}
/>
</PageShell>
@@ -1,5 +1,6 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { NoteLegend } from '@/components/NoteLegend'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect, Link } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -15,6 +16,7 @@ export const metadata: Metadata = { title: 'Transferir personaje' }
export default async function TransferCharacterPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('CharService')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -23,35 +25,33 @@ export default async function TransferCharacterPage({ params }: { params: Promis
const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getTransferPrice()])
return (
<PageShell title="Transferir personaje">
<PageShell title={t('transfer.pageTitle')}>
<ServiceBox>
<br />
<p>
La herramienta <span>Transferir personaje</span> te permite mover un personaje a otra cuenta.
{t.rich('transfer.intro', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>Para personajes <span className="death-knight">Caballeros de la Muerte</span>:</p>
<p>- La cuenta destino debe tener aunque sea un personaje con nivel 55 o superior.</p>
<p>- La cuenta destino no puede tener un personaje Caballero de la Muerte.</p>
<p>{t.rich('transfer.dkHeading', { dk: (c) => <span className="death-knight">{c}</span> })}</p>
<p>{t('transfer.dkCond1')}</p>
<p>{t('transfer.dkCond2')}</p>
<br />
<p>Tu cuenta será bloqueada por 5s como medida de seguridad si el trámite es exitoso.</p>
<p>{t('transfer.securityLock')}</p>
<br />
<p>
Al usar el botón TRANSFERIR PERSONAJE del personaje que hayas escogido, se transferirá desde tu cuenta a la
cuenta que hayas indicado.
{t('transfer.buttonExplain')}
</p>
<p>Cuando ingreses a la lista de personajes de la cuenta destino, verás al personaje transferido.</p>
<p>{t('transfer.afterTransfer')}</p>
<br />
<p>Si aún no tienes Token de seguridad, puedes solicitarlo <Link href="/security-token" target="_blank">aquí</Link>.</p>
<p>Si aún no tienes 2FA, puedes solicitarlo <Link href="/security-2falogin" target="_blank">aquí</Link>.</p>
<p>{t.rich('transfer.securityTokenHint', { link: (c) => <Link href="/security-token" target="_blank">{c}</Link> })}</p>
<p>{t.rich('transfer.twoFaHint', { link: (c) => <Link href="/security-2falogin" target="_blank">{c}</Link> })}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<NoteLegend />
<div className="separate2">
<p>Se requiere que el personaje esté desconectado.</p>
<p>{t('transfer.noteOffline')}</p>
<p>
Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez
realizada.
{t('transfer.noteIrreversible')}
</p>
</div>
</fieldset>
@@ -59,7 +59,7 @@ export default async function TransferCharacterPage({ params }: { params: Promis
<div className="centered">
<br />
<br />
<p>Requiere <span>{price}</span> <span className="yellow-info"></span> (SumUp)</p>
<p>{t.rich('transfer.requires', { price, s: (c) => <span>{c}</span>, e: (c) => <span className="yellow-info">{c}</span> })}</p>
</div>
<TransferForm characters={chars.map((c) => ({ name: c.name, classCss: c.classCss }))} price={price} />
@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import { setRequestLocale } from 'next-intl/server'
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { redirect, Link } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getDPointsBalance } from '@/lib/dpoints'
@@ -14,6 +14,7 @@ export const metadata: Metadata = { title: 'Transferir PD' }
export default async function TransferDPointsPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
const t = await getTranslations('Points')
const session = await getSession()
if (!session.bnetId) redirect({ href: '/login', locale })
@@ -22,29 +23,32 @@ export default async function TransferDPointsPage({ params }: { params: Promise<
const balance = await getDPointsBalance(session.accountId!)
return (
<PageShell title="Transferir PD">
<PageShell title={t('transferDp.title')}>
<ServiceBox>
<br />
<p>
La herramienta <span>Transferir PD</span> permite transferir PD desde tu cuenta a cualquier otra cuenta.
{t.rich('transferDp.intro1', { s: (c) => <span>{c}</span> })}
</p>
<br />
<p>
Al usar el botón TRANSFERIR PD se transferirá la cantidad que hayas escogido desde tu cuenta a la cuenta del
personaje que hayas indicado.
{t('transferDp.intro2')}
</p>
<br />
<p>
Si aún no tienes Token de seguridad, puedes solicitarlo{' '}
<Link href="/security-token" target="_blank">aquí</Link>.
{t.rich('transferDp.tokenHelp', {
link: (c) => (
<Link href="/security-token" target="_blank">
{c}
</Link>
),
})}
</p>
<br />
<fieldset>
<legend>NOTA</legend>
<legend>{t('transferDp.note')}</legend>
<div className="separate2">
<p>
Por favor, asegúrate de haber escrito el nombre del personaje correcto ya que esta acción no es reversible
una vez realizada.
{t('transferDp.noteText')}
</p>
</div>
</fieldset>
@@ -1,4 +1,5 @@
import { getTranslations, setRequestLocale } from 'next-intl/server'
import { NoteLegend } from '@/components/NoteLegend'
import { redirect } from '@/i18n/navigation'
import { getSession } from '@/lib/session'
import { getGameCharacters } from '@/lib/characters'
@@ -25,7 +26,7 @@ export default async function UnstuckPage({ params }: { params: Promise<{ locale
<p>{t('unstuckInfo2')}</p>
<br />
<fieldset>
<legend>NOTA</legend>
<NoteLegend />
<div className="separate2">
<p>{t('unstuckNote1')}</p>
<p>{t('unstuckNote2')}</p>