Files
Epicnabbo-Catalogus-Updated…/src/app/admin/calendar/[id]/page.tsx
T
openhands 928544100b Convert admin pages to new Tailwind design pattern
- Replace 'card' class with 'admin-card' (gradient cards, dark mode support)
- Replace 'muted' class with consistent Tailwind muted states
- Standardize page headers with gradient backgrounds and Lucide icons
- Convert all inline styles to Tailwind classes
- Add admin-page CSS helpers (admin-card, admin-filter-bar, admin-section-title)
- Apply pattern to all 42 admin pages (was only 6 before)
- Zero TypeScript errors, production builds successfully

Co-authored-by: openhands <[email protected]>
2026-07-02 16:05:35 +02:00

169 lines
4.9 KiB
TypeScript

import Link from "next/link";
import { notFound } from "next/navigation";
import { prisma } from "@/lib/prisma";
import { Calendar } from "lucide-react";
export const dynamic = "force-dynamic";
type Campaign = {
id: number;
name: string;
image: string;
startTimestamp: number;
totalDays: number;
lockExpired: string;
enabled: string;
};
type Reward = {
id: number;
campaignId: number;
productName: string;
customImage: string;
credits: number;
pixels: number;
points: number;
pointsType: number;
badge: string;
itemId: number;
subscriptionType: string | null;
subscriptionDays: number;
};
function fmtDate(ts: number): string {
if (!ts) return "—";
return new Date(ts * 1000).toISOString().slice(0, 10);
}
export default async function AdminCalendarCampaign({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const campaignId = Number(id);
if (!Number.isInteger(campaignId)) notFound();
let campaign: Campaign | null = null;
try {
campaign = await prisma.calendarCampaigns.findUnique({
where: { id: campaignId },
select: {
id: true,
name: true,
image: true,
startTimestamp: true,
totalDays: true,
lockExpired: true,
enabled: true,
},
});
} catch {
campaign = null;
}
if (!campaign) notFound();
let rewards: Reward[] = [];
try {
rewards = await prisma.calendarRewards.findMany({
where: { campaignId },
select: {
id: true,
campaignId: true,
productName: true,
customImage: true,
credits: true,
pixels: true,
points: true,
pointsType: true,
badge: true,
itemId: true,
subscriptionType: true,
subscriptionDays: true,
},
orderBy: { id: "asc" },
take: 500,
});
} catch {
rewards = [];
}
const disabled = campaign.enabled !== "1";
return (
<main>
<p className="text-xs text-gray-500 dark:text-gray-400 mb-4">
<Link href="/admin/calendar">← Calendar campaigns</Link>
</p>
<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}
/>
) : null}
<div>
<h1 className="m-0 mb-1">{campaign.name || `Campaign #${campaign.id}`}</h1>
<p className="text-xs text-gray-500 dark:text-gray-400 m-0">
#{campaign.id} · {campaign.totalDays} day{campaign.totalDays === 1 ? "" : "s"} · starts{" "}
{fmtDate(campaign.startTimestamp)}
{disabled ? " · Disabled" : " · Enabled"}
{campaign.lockExpired === "1" ? " · Locks expired days" : ""}
</p>
</div>
</div>
<h2 className="admin-section-title">Rewards</h2>
{rewards.length === 0 ? (
<div className="admin-empty">No rewards configured for this campaign.</div>
) : (
<table className="w-full">
<thead>
<tr className="text-left text-[var(--muted)]">
<th>ID</th>
<th>Product</th>
<th>Item</th>
<th>Credits</th>
<th>Pixels</th>
<th>Points</th>
<th>Badge</th>
<th>Subscription</th>
</tr>
</thead>
<tbody>
{rewards.map((r) => (
<tr key={r.id} className="border-t border-[var(--border)]">
<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.points > 0 ? (
`${r.points} (type ${r.pointsType})`
) : (
<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`
: "—"}
</td>
</tr>
))}
</tbody>
</table>
)}
<p className="text-xs text-gray-500 dark:text-gray-400 mt-4">
{rewards.length} reward{rewards.length === 1 ? "" : "s"}
</p>
</main>
);
}