Add missing admin action files and navigation links

- Add 11 missing server action files: badges, bulk-users, catalog, catalog-bc, catalog-items, import-badges, import-furni, multi-account-detect, permissions, rooms, soundtracks
- Add missing admin navigation links: tickets, sounds, translations, import, radio sub-pages
- Add translation keys for all new navigation items
This commit is contained in:
openhands committed 2026-07-11 12:01:05 +02:00
1 parent 7e1ae17a3b
commit 41be6835bf
347 files changed
+28378 -6091

No files matched your search

+31 -19
View File
@@ -35,11 +35,7 @@ function fmtDate(ts: number): string {
return new Date(ts * 1000).toISOString().slice(0, 10);
}
export default async function AdminCalendarCampaign({
params,
}: {
params: Promise<{ id: string }>;
}) {
export default async function AdminCalendarCampaign({ params }: { params: Promise<{ id: string }> }) {
const t = await getTranslations("pages.admin.calendar");
const { id } = await params;
@@ -101,16 +97,16 @@ export default async function AdminCalendarCampaign({
<div className="admin-card flex items-center gap-5 mb-6">
{campaign.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img
className="w-24 h-24 object-cover"
src={campaign.image}
alt={campaign.name}
/>
<img className="w-24 h-24 object-cover" src={campaign.image} alt={campaign.name} />
) : null}
<div>
<h1 className="m-0 mb-1">{campaign.name || `#${campaign.id}`}</h1>
<p className="text-xs text-gray-500 dark:text-gray-400 m-0">
{t("detail.campaignMeta", { id: campaign.id, days: campaign.totalDays, date: fmtDate(campaign.startTimestamp) })}
{t("detail.campaignMeta", {
id: campaign.id,
days: campaign.totalDays,
date: fmtDate(campaign.startTimestamp),
})}
{disabled ? ` · ${t("detail.disabled")}` : ` · ${t("detail.enabled")}`}
{campaign.lockExpired === "1" ? ` · ${t("detail.locksExpired")}` : ""}
</p>
@@ -138,10 +134,26 @@ export default async function AdminCalendarCampaign({
{rewards.map((r) => (
<tr key={r.id} className="border-t border-[var(--border-subtle)]">
<td>{r.id}</td>
<td>{r.productName || <span className="text-sm text-gray-500 dark:text-gray-400">—</span>}</td>
<td className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{r.itemId > 0 ? r.itemId : "—"}</td>
<td>{r.credits > 0 ? r.credits : <span className="text-sm text-gray-500 dark:text-gray-400">—</span>}</td>
<td>{r.pixels > 0 ? r.pixels : <span className="text-sm text-gray-500 dark:text-gray-400">—</span>}</td>
<td>
{r.productName || <span className="text-sm text-gray-500 dark:text-gray-400">—</span>}
</td>
<td className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
{r.itemId > 0 ? r.itemId : "—"}
</td>
<td>
{r.credits > 0 ? (
r.credits
) : (
<span className="text-sm text-gray-500 dark:text-gray-400">—</span>
)}
</td>
<td>
{r.pixels > 0 ? (
r.pixels
) : (
<span className="text-sm text-gray-500 dark:text-gray-400">—</span>
)}
</td>
<td>
{r.points > 0 ? (
`${r.points} (${t("detail.typeLabel", { type: r.pointsType })})`
@@ -149,11 +161,11 @@ export default async function AdminCalendarCampaign({
<span className="text-sm text-gray-500 dark:text-gray-400">—</span>
)}
</td>
<td className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">{r.badge || "—"}</td>
<td className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
{r.subscriptionType
? `${r.subscriptionType} · ${r.subscriptionDays}d`
: "—"}
{r.badge || "—"}
</td>
<td className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
{r.subscriptionType ? `${r.subscriptionType} · ${r.subscriptionDays}d` : "—"}
</td>
</tr>
))}
+8 -9
View File
@@ -52,8 +52,7 @@ export default async function AdminCalendar() {
for (const r of rewards) {
rewardCounts.set(r.campaignId, (rewardCounts.get(r.campaignId) ?? 0) + 1);
}
} catch {
}
} catch {}
const enabledCount = campaigns.filter((c) => c.enabled === "1").length;
@@ -65,7 +64,9 @@ export default async function AdminCalendar() {
</div>
<div>
<h1 className="m-0 text-xl font-extrabold text-[var(--color-text)]">{t("title")}</h1>
<p className="m-0 text-xs text-[var(--color-text-muted)] mt-0.5">{t("subtitle", { count: campaigns.length })}</p>
<p className="m-0 text-xs text-[var(--color-text-muted)] mt-0.5">
{t("subtitle", { count: campaigns.length })}
</p>
</div>
</div>
@@ -88,11 +89,7 @@ export default async function AdminCalendar() {
const enabled = c.enabled === "1";
const count = rewardCounts.get(c.id) ?? 0;
return (
<Link
key={c.id}
href={`/admin/calendar/${c.id}`}
className="admin-card hover article"
>
<Link key={c.id} href={`/admin/calendar/${c.id}`} className="admin-card hover article">
{c.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="article-img" src={c.image} alt={c.name} />
@@ -102,7 +99,9 @@ export default async function AdminCalendar() {
<div className="body">
<div className="flex items-center justify-between gap-2 mb-1">
<h3 className="m-0">{c.name || `#${c.id}`}</h3>
<span className={`inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full ${enabled ? "bg-green-600/20 text-[var(--color-text)]" : "bg-red-500/22 text-[var(--color-text)]"}`}>
<span
className={`inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full ${enabled ? "bg-green-600/20 text-[var(--color-text)]" : "bg-red-500/22 text-[var(--color-text)]"}`}
>
{enabled ? t("statusEnabled") : t("statusDisabled")}
</span>
</div>