diff --git a/web-next/app/[locale]/admin/page.tsx b/web-next/app/[locale]/admin/page.tsx index 393439b..9923b4b 100644 --- a/web-next/app/[locale]/admin/page.tsx +++ b/web-next/app/[locale]/admin/page.tsx @@ -22,6 +22,11 @@ export default async function AdminPage({ params }: { params: Promise<{ locale: {t('manageNews')} +
  • + + {t('managePrices')} + +
  • ) diff --git a/web-next/app/[locale]/admin/prices/page.tsx b/web-next/app/[locale]/admin/prices/page.tsx new file mode 100644 index 0000000..2e2f494 --- /dev/null +++ b/web-next/app/[locale]/admin/prices/page.tsx @@ -0,0 +1,24 @@ +import { getTranslations, setRequestLocale } from 'next-intl/server' +import { redirect } from '@/i18n/navigation' +import { getSession } from '@/lib/session' +import { isAdmin } from '@/lib/admin' +import { getAllPrices } from '@/lib/admin-prices' +import { AdminPricesManager } from '@/components/AdminPricesManager' + +export const dynamic = 'force-dynamic' + +export default async function AdminPricesPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations('Admin') + const session = await getSession() + if (!session.bnetId) redirect({ href: '/login', locale }) + if (!(await isAdmin(session))) redirect({ href: '/', locale }) + const prices = await getAllPrices() + return ( +
    +

    {t('prices')}

    + +
    + ) +} diff --git a/web-next/app/api/admin/prices/route.ts b/web-next/app/api/admin/prices/route.ts new file mode 100644 index 0000000..5351c8d --- /dev/null +++ b/web-next/app/api/admin/prices/route.ts @@ -0,0 +1,15 @@ +import { getSession } from '@/lib/session' +import { isAdmin } from '@/lib/admin' +import { setPrice } from '@/lib/admin-prices' + +export async function POST(request: Request) { + const session = await getSession() + if (!(await isAdmin(session))) return Response.json({ success: false, error: 'forbidden' }, { status: 403 }) + let b: Record = {} + try { b = await request.json() } catch { return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 }) } + const service = String(b.service ?? '') + const value = Number(b.value) + if (!service || !(value >= 0)) return Response.json({ success: false, error: 'invalidRequest' }) + const ok = await setPrice(service, value) + return Response.json({ success: ok }) +} diff --git a/web-next/components/AdminPricesManager.tsx b/web-next/components/AdminPricesManager.tsx new file mode 100644 index 0000000..27a117d --- /dev/null +++ b/web-next/components/AdminPricesManager.tsx @@ -0,0 +1,62 @@ +'use client' + +import { useState } from 'react' +import { useTranslations } from 'next-intl' +import type { PriceRow } from '@/lib/admin-prices' + +function Row({ row }: { row: PriceRow }) { + const t = useTranslations('Admin') + const [value, setValue] = useState(String(row.price)) + const [busy, setBusy] = useState(false) + const [saved, setSaved] = useState(false) + + async function save() { + setBusy(true) + setSaved(false) + try { + const res = await fetch('/api/admin/prices', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'same-origin', + body: JSON.stringify({ service: row.service, value: Number(value) }), + }) + const data: { success?: boolean } = await res.json() + if (data.success) { + setSaved(true) + setTimeout(() => setSaved(false), 2000) + } + } finally { + setBusy(false) + } + } + + return ( +
  • + {t(`svc_${row.service}`)} +
    + setValue(e.target.value)} + className="w-24 rounded border border-amber-900/60 bg-[#2c1e14] px-2 py-1 text-right" + /> + + {saved && {t('saved')}} +
    +
  • + ) +} + +export function AdminPricesManager({ prices }: { prices: PriceRow[] }) { + return ( + + ) +} diff --git a/web-next/lib/admin-prices.ts b/web-next/lib/admin-prices.ts new file mode 100644 index 0000000..d67c49d --- /dev/null +++ b/web-next/lib/admin-prices.ts @@ -0,0 +1,41 @@ +import type { RowDataPacket } from 'mysql2' +import { db, DB } from './db' + +// Servicios con precio único (una fila) -> tabla django_wow. +export const PRICE_TABLES: Record = { + rename: 'home_renameprice', + customize: 'home_customizeprice', + 'change-race': 'home_changeraceprice', + 'change-faction': 'home_changefactionprice', + 'level-up': 'home_levelupprice', + transfer: 'home_transferprice', +} + +export interface PriceRow { + service: string + price: number +} + +export async function getAllPrices(): Promise { + const out: PriceRow[] = [] + for (const [service, table] of Object.entries(PRICE_TABLES)) { + let price = 0 + try { + const [rows] = await db(DB.default).query(`SELECT price FROM ${table} LIMIT 1`) + price = rows[0] ? Number(rows[0].price) : 0 + } catch { + /* ignore */ + } + out.push({ service, price }) + } + return out +} + +/** Fija el precio (fila única) de un servicio. */ +export async function setPrice(service: string, value: number): Promise { + const table = PRICE_TABLES[service] + if (!table || !(value >= 0)) return false + await db(DB.default).query(`DELETE FROM ${table}`) + await db(DB.default).query(`INSERT INTO ${table} (price) VALUES (?)`, [value]) + return true +} diff --git a/web-next/messages/en.json b/web-next/messages/en.json index 73fd56e..ce17bb7 100644 --- a/web-next/messages/en.json +++ b/web-next/messages/en.json @@ -281,6 +281,17 @@ "noNews": "No news.", "forbidden": "You do not have permission to access here.", "emptyError": "Fill in the title and content.", - "genericError": "Something went wrong." + "genericError": "Something went wrong.", + "managePrices": "Manage prices", + "prices": "Service prices", + "price": "Price (€)", + "save": "Save", + "saved": "Saved", + "svc_rename": "Rename character", + "svc_customize": "Customize", + "svc_change-race": "Change race", + "svc_change-faction": "Change faction", + "svc_level-up": "Level up to 80", + "svc_transfer": "Transfer character" } } diff --git a/web-next/messages/es.json b/web-next/messages/es.json index 9007def..e5d1042 100644 --- a/web-next/messages/es.json +++ b/web-next/messages/es.json @@ -281,6 +281,17 @@ "noNews": "No hay noticias.", "forbidden": "No tienes permiso para acceder aquí.", "emptyError": "Completa el título y el contenido.", - "genericError": "Algo ha salido mal." + "genericError": "Algo ha salido mal.", + "managePrices": "Gestionar precios", + "prices": "Precios de servicios", + "price": "Precio (€)", + "save": "Guardar", + "saved": "Guardado", + "svc_rename": "Renombrar personaje", + "svc_customize": "Personalizar", + "svc_change-race": "Cambiar raza", + "svc_change-faction": "Cambiar facción", + "svc_level-up": "Subir a nivel 80", + "svc_transfer": "Transferir personaje" } }