diff --git a/app/[locale]/admin/page.tsx b/app/[locale]/admin/page.tsx index c20ab6f..c080b1f 100644 --- a/app/[locale]/admin/page.tsx +++ b/app/[locale]/admin/page.tsx @@ -15,6 +15,7 @@ interface Special { launchDate?: string; endDate?: string; curator?: string; + hidden?: boolean; _count?: { songs: number; }; @@ -119,6 +120,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { const [newSpecialLaunchDate, setNewSpecialLaunchDate] = useState(''); const [newSpecialEndDate, setNewSpecialEndDate] = useState(''); const [newSpecialCurator, setNewSpecialCurator] = useState(''); + const [newSpecialHidden, setNewSpecialHidden] = useState(false); const [editingSpecialId, setEditingSpecialId] = useState(null); const [editSpecialName, setEditSpecialName] = useState({ de: '', en: '' }); @@ -129,6 +131,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { const [editSpecialLaunchDate, setEditSpecialLaunchDate] = useState(''); const [editSpecialEndDate, setEditSpecialEndDate] = useState(''); const [editSpecialCurator, setEditSpecialCurator] = useState(''); + const [editSpecialHidden, setEditSpecialHidden] = useState(false); // News state const [news, setNews] = useState([]); @@ -393,6 +396,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { launchDate: newSpecialLaunchDate || null, endDate: newSpecialEndDate || null, curator: newSpecialCurator || null, + hidden: newSpecialHidden, }), }); if (res.ok) { @@ -404,6 +408,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { setNewSpecialLaunchDate(''); setNewSpecialEndDate(''); setNewSpecialCurator(''); + setNewSpecialHidden(false); fetchSpecials(); } else { const errorData = await res.json().catch(() => ({})); @@ -491,6 +496,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { setEditSpecialLaunchDate(special.launchDate ? new Date(special.launchDate).toISOString().split('T')[0] : ''); setEditSpecialEndDate(special.endDate ? new Date(special.endDate).toISOString().split('T')[0] : ''); setEditSpecialCurator(special.curator || ''); + setEditSpecialHidden(special.hidden || false); }; const saveEditedSpecial = async () => { @@ -516,6 +522,7 @@ export default function AdminPage({ params }: { params: { locale: string } }) { launchDate: editSpecialLaunchDate || null, endDate: editSpecialEndDate || null, curator: editSpecialCurator || null, + hidden: editSpecialHidden, }), }); if (res.ok) { @@ -1389,6 +1396,18 @@ export default function AdminPage({ params }: { params: { locale: string } }) { setNewSpecialCurator(e.target.value)} className="form-input" /> +
+ + +
@@ -651,7 +667,7 @@ export default function AdminPage() { }} > - {special.name} ({special._count?.songs || 0}) + {special.hidden && 👁️‍🗨️} {special.name} ({special._count?.songs || 0}) {special.subtitle && ( setEditSpecialCurator(e.target.value)} className="form-input" /> + diff --git a/app/api/specials/[id]/route.ts b/app/api/specials/[id]/route.ts index bca2c54..0544fd9 100644 --- a/app/api/specials/[id]/route.ts +++ b/app/api/specials/[id]/route.ts @@ -43,18 +43,20 @@ export async function PUT( try { const { id } = await params; const specialId = parseInt(id); - const { name, maxAttempts, unlockSteps, launchDate, endDate, curator } = await request.json(); + const { name, maxAttempts, unlockSteps, launchDate, endDate, curator, hidden } = await request.json(); + + const updateData: any = {}; + if (name !== undefined) updateData.name = name; + if (maxAttempts !== undefined) updateData.maxAttempts = maxAttempts; + if (unlockSteps !== undefined) updateData.unlockSteps = typeof unlockSteps === 'string' ? unlockSteps : JSON.stringify(unlockSteps); + if (launchDate !== undefined) updateData.launchDate = launchDate ? new Date(launchDate) : null; + if (endDate !== undefined) updateData.endDate = endDate ? new Date(endDate) : null; + if (curator !== undefined) updateData.curator = curator || null; + if (hidden !== undefined) updateData.hidden = Boolean(hidden); const special = await prisma.special.update({ where: { id: specialId }, - data: { - name, - maxAttempts, - unlockSteps: typeof unlockSteps === 'string' ? unlockSteps : JSON.stringify(unlockSteps), - launchDate: launchDate ? new Date(launchDate) : null, - endDate: endDate ? new Date(endDate) : null, - curator: curator || null, - } + data: updateData }); return NextResponse.json(special); diff --git a/app/api/specials/route.ts b/app/api/specials/route.ts index 0c58d82..f66da08 100644 --- a/app/api/specials/route.ts +++ b/app/api/specials/route.ts @@ -35,7 +35,7 @@ export async function POST(request: Request) { const authError = await requireAdminAuth(request as any); if (authError) return authError; - const { name, subtitle, maxAttempts = 7, unlockSteps = '[2,4,7,11,16,30,60]', launchDate, endDate, curator } = await request.json(); + const { name, subtitle, maxAttempts = 7, unlockSteps = '[2,4,7,11,16,30,60]', launchDate, endDate, curator, hidden = false } = await request.json(); if (!name) { return NextResponse.json({ error: 'Name is required' }, { status: 400 }); } @@ -68,6 +68,7 @@ export async function POST(request: Request) { launchDate: launchDate ? new Date(launchDate) : null, endDate: endDate ? new Date(endDate) : null, curator: curator || null, + hidden: Boolean(hidden), }, }); return NextResponse.json(special); @@ -91,7 +92,7 @@ export async function PUT(request: Request) { const authError = await requireAdminAuth(request as any); if (authError) return authError; - const { id, name, subtitle, maxAttempts, unlockSteps, launchDate, endDate, curator } = await request.json(); + const { id, name, subtitle, maxAttempts, unlockSteps, launchDate, endDate, curator, hidden } = await request.json(); if (!id) { return NextResponse.json({ error: 'ID required' }, { status: 400 }); } @@ -119,6 +120,7 @@ export async function PUT(request: Request) { if (launchDate !== undefined) updateData.launchDate = launchDate ? new Date(launchDate) : null; if (endDate !== undefined) updateData.endDate = endDate ? new Date(endDate) : null; if (curator !== undefined) updateData.curator = curator || null; + if (hidden !== undefined) updateData.hidden = Boolean(hidden); const updated = await prisma.special.update({ where: { id: Number(id) }, diff --git a/prisma/migrations/20251206002217_add_hidden_to_special/migration.sql b/prisma/migrations/20251206002217_add_hidden_to_special/migration.sql new file mode 100644 index 0000000..03c5481 --- /dev/null +++ b/prisma/migrations/20251206002217_add_hidden_to_special/migration.sql @@ -0,0 +1,20 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Special" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" JSONB NOT NULL, + "subtitle" JSONB, + "maxAttempts" INTEGER NOT NULL DEFAULT 7, + "unlockSteps" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "launchDate" DATETIME, + "endDate" DATETIME, + "curator" TEXT, + "hidden" BOOLEAN NOT NULL DEFAULT false +); +INSERT INTO "new_Special" ("createdAt", "curator", "endDate", "id", "launchDate", "maxAttempts", "name", "subtitle", "unlockSteps") SELECT "createdAt", "curator", "endDate", "id", "launchDate", "maxAttempts", "name", "subtitle", "unlockSteps" FROM "Special"; +DROP TABLE "Special"; +ALTER TABLE "new_Special" RENAME TO "Special"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 40330a5..12248a2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -47,6 +47,7 @@ model Special { launchDate DateTime? endDate DateTime? curator String? + hidden Boolean @default(false) songs SpecialSong[] puzzles DailyPuzzle[] news News[]