Add dynamic footer badge marquee with admin management.

Store badges in Postgres, expose public/admin APIs, render an infinite footer slider, and ship /admin for paste-embed CRUD.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Atakan Doğan Özban
2026-08-01 19:58:24 +02:00
co-authored by Cursor
parent 42ad9c38e3
commit 7d22f722f6
21 changed files with 1027 additions and 20 deletions
+48
View File
@@ -0,0 +1,48 @@
import { NextRequest, NextResponse } from "next/server";
import { requireAdmin } from "@/lib/admin-auth";
import { prisma } from "@/lib/db";
import { normalizeBadgeFields, validateBadgeFields } from "@/lib/footer-badges";
export async function GET(req: NextRequest) {
const denied = requireAdmin(req);
if (denied) return denied;
const badges = await prisma.footerBadge.findMany({
orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
});
return NextResponse.json({ badges });
}
export async function POST(req: NextRequest) {
const denied = requireAdmin(req);
if (denied) return denied;
let body: unknown;
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
}
const input = (body ?? {}) as Record<string, unknown>;
const fields = normalizeBadgeFields({
name: typeof input.name === "string" ? input.name : "",
embedHtml: typeof input.embedHtml === "string" ? input.embedHtml : null,
imageUrl: typeof input.imageUrl === "string" ? input.imageUrl : null,
linkUrl: typeof input.linkUrl === "string" ? input.linkUrl : null,
isActive: typeof input.isActive === "boolean" ? input.isActive : true,
sortOrder: typeof input.sortOrder === "number" ? input.sortOrder : undefined,
});
const error = validateBadgeFields(fields);
if (error) return NextResponse.json({ error }, { status: 400 });
if (fields.sortOrder === 0) {
const max = await prisma.footerBadge.aggregate({ _max: { sortOrder: true } });
fields.sortOrder = (max._max.sortOrder ?? -1) + 1;
}
const badge = await prisma.footerBadge.create({ data: fields });
return NextResponse.json({ badge }, { status: 201 });
}