Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 3x 3x 3x 1x 1x 3x 3x 3x 3x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x 10x 3x 3x 7x 7x 7x 7x 7x 7x 7x 10x 1x 1x 6x 6x 6x 6x 6x 6x 6x 10x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 4x 4x 4x 1x 1x 1x | export const dynamic = "force-dynamic";
/**
* Admin Hero Feature Cards API
* CRUD operations for hero feature cards (side cards)
*/
import { NextRequest, NextResponse } from 'next/server';
import { } from "next-auth";
import { prisma } from "@/lib/prisma";
import { z } from "zod";
import {
withAdmin,
withErrorHandling,
successResponse,
createdResponse,
ApiError,
ApiSuccessResponse,
ApiErrorResponse } from "@/lib/api";
import { } from "@/lib/api/middleware";
// Validation schema for creating feature card
const createFeatureCardSchema = z.object({
productId: z.number().int().positive(),
title: z.string().max(200).optional().nullable(),
subtitle: z.string().max(100).optional().nullable(),
position: z.enum(["top", "bottom"]),
isActive: z.boolean().default(true) });
/**
* GET /api/admin/hero/feature-cards
* List all feature cards
*/
async function handleGet(): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> {
const cards = await prisma.heroFeatureCard.findMany({
orderBy: { position: "asc" },
include: {
product: {
select: {
id: true,
title: true,
price: true,
discountedPrice: true,
images: {
select: {
id: true,
url: true,
thumbnailUrl: true },
orderBy: { order: "asc" },
take: 1 } } } } });
// Sort: top first, then bottom
const sortedCards = cards.sort((a, b) => {
if (a.position === "top" && b.position === "bottom") return -1;
if (a.position === "bottom" && b.position === "top") return 1;
return 0;
});
return successResponse({ cards: sortedCards });
}
/**
* POST /api/admin/hero/feature-cards
* Create new feature card
*/
async function handlePost(request: NextRequest): Promise<NextResponse<ApiSuccessResponse<unknown> | ApiErrorResponse>> {
const body = await request.json();
// Validate input
const validationResult = createFeatureCardSchema.safeParse(body);
if (!validationResult.success) {
throw ApiError.validation("Validation failed", validationResult.error.issues);
}
const validatedData = validationResult.data;
// Check if product exists
const product = await prisma.product.findUnique({
where: { id: validatedData.productId } });
if (!product) {
throw ApiError.notFound("Product");
}
// Check if position is already taken by an active card
const existingCard = await prisma.heroFeatureCard.findFirst({
where: {
position: validatedData.position,
isActive: true } });
if (existingCard) {
throw ApiError.badRequest(
`A feature card already exists in the ${validatedData.position} position. Delete or deactivate it first, or update the existing card.`
);
}
const card = await prisma.heroFeatureCard.create({
data: {
productId: validatedData.productId,
title: validatedData.title,
subtitle: validatedData.subtitle,
position: validatedData.position,
isActive: validatedData.isActive },
include: {
product: {
select: {
id: true,
title: true,
price: true,
discountedPrice: true,
images: {
select: {
id: true,
url: true,
thumbnailUrl: true },
orderBy: { order: "asc" },
take: 1 } } } } });
return createdResponse({ card });
}
export const GET = withErrorHandling(withAdmin(handleGet));
export const POST = withErrorHandling(withAdmin(handlePost));
|