Files
NightSpire/web-next/components/AdminRecruitManager.tsx
T
Inna fa7897c195 Prefijos del tema nw- y uw- -> ns- (carpetas, ficheros, clases y rutas)
Renombrado completo de los prefijos heredados: 19 carpetas, 11 ficheros y 318
referencias en 35 ficheros de código, más las clases y las rutas url() de dentro
del CSS del tema. Se usa git mv para conservar el historial.

También fuera del código, que un sed no ve:
- BD: votesite.image_url (4 filas) apuntaba a /nw-themes/...
- Ficheros con la marca vieja en el NOMBRE: novawow-maintenance.webp ->
  nightspire-maintenance.webp (lo usa la página de mantenimiento) y
  store_novawow_response.js.

⚠ Alias en Caddy /nw-themes/* -> /ns-themes/*: los correos ENVIADOS antes del
rebranding llevan esas rutas escritas y están en las bandejas de los usuarios.
Sin el alias, sus imágenes se romperían. Verificado que las rutas viejas siguen
sirviendo 200.

Nota: ns-js/ y ns-js-handlers/ son CÓDIGO MUERTO (manejadores jQuery del portal
Django, que ya se borró). Se midió en el navegador: la web no pide ni un solo JS
del tema. Se renombran igualmente por consistencia, pero son candidatos a
borrarse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 15:50:43 +00:00

105 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState } from 'react'
import { useTranslations } from 'next-intl'
import type { RecruitReward } from '@/lib/admin-recruit'
const EMPTY = { requiredFriends: '', rewardName: '', itemId: '', itemQuantity: '1', itemLink: '', iconClass: '' }
export function AdminRecruitManager({ initial }: { initial: RecruitReward[] }) {
const t = useTranslations('Admin')
const [rewards, setRewards] = useState(initial)
const [form, setForm] = useState(EMPTY)
const [busy, setBusy] = useState(false)
function upd(k: string, v: string) {
setForm((f) => ({ ...f, [k]: v }))
}
async function create(e: React.FormEvent) {
e.preventDefault()
if (busy || !(Number(form.requiredFriends) > 0) || !form.rewardName.trim() || !(Number(form.itemId) > 0)) return
setBusy(true)
try {
const res = await fetch('/api/admin/recruit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify(form),
})
const data: { success?: boolean; id?: number } = await res.json()
if (data.success && data.id) {
setRewards(
[
...rewards,
{
id: data.id,
required_friends: Number(form.requiredFriends),
reward_name: form.rewardName,
item_id: Number(form.itemId),
item_quantity: Number(form.itemQuantity) || 1,
item_link: form.itemLink,
icon_class: form.iconClass,
},
].sort((a, b) => a.required_friends - b.required_friends),
)
setForm(EMPTY)
}
} finally {
setBusy(false)
}
}
async function remove(id: number) {
if (!confirm(t('confirmDelete'))) return
const res = await fetch(`/api/admin/recruit/${id}`, { method: 'DELETE', credentials: 'same-origin' })
if ((await res.json()).success) setRewards(rewards.filter((r) => r.id !== id))
}
return (
<div>
<form onSubmit={create} className="admin-form info-box-light separate2">
<span className="second-brown">{t('requiredFriends')}</span>
<input type="number" min="1" value={form.requiredFriends} onChange={(e) => upd('requiredFriends', e.target.value)} placeholder="1" />
<span className="second-brown">{t('rewardName')}</span>
<input value={form.rewardName} onChange={(e) => upd('rewardName', e.target.value)} placeholder="Montura épica" />
<span className="second-brown">{t('itemId')}</span>
<input type="number" min="1" value={form.itemId} onChange={(e) => upd('itemId', e.target.value)} placeholder="49284" />
<span className="second-brown">{t('itemQuantity')}</span>
<input type="number" min="1" value={form.itemQuantity} onChange={(e) => upd('itemQuantity', e.target.value)} />
<span className="second-brown">{t('itemLink')}</span>
<input value={form.itemLink} onChange={(e) => upd('itemLink', e.target.value)} placeholder="https://…" />
<span className="second-brown">{t('iconClass')}</span>
<input value={form.iconClass} onChange={(e) => upd('iconClass', e.target.value)} placeholder="icontinyl q3" />
<button type="submit" disabled={busy}>
{busy ? t('creating') : t('create')}
</button>
</form>
<br />
{rewards.length === 0 ? (
<p className="second-brown centered">{t('noRewards')}</p>
) : (
<table className="max-center-table">
<tbody>
{rewards.map((r) => (
<tr key={r.id} className="team-center-table-tr">
<td className="lefted separate">
<span className="yellow-info">{r.reward_name}</span>
<p className="third-brown small-font">
{t('requiredFriendsShort', { n: r.required_friends })} · item {r.item_id} ×{r.item_quantity}
</p>
</td>
<td className="real-info-box">
<button onClick={() => remove(r.id)} className="ns-tool-btn red-info2">
{t('delete')}
</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
)
}