Implement portfolio admin management
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { PortfolioAssetKind, PortfolioSectionType } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
|
||||
import { mediaFieldInputSchema } from "./media-validation";
|
||||
|
||||
const requiredText = (label: string) =>
|
||||
z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, `${label} is required.`);
|
||||
|
||||
const optionalTrimmedText = z.string().trim().optional().transform((value) => value ?? "");
|
||||
|
||||
export const categoryInputSchema = z.object({
|
||||
id: z.string().trim().optional(),
|
||||
slug: requiredText("Category slug")
|
||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Category slug must be lowercase and hyphenated."),
|
||||
nameAr: requiredText("Category nameAr"),
|
||||
nameEn: requiredText("Category nameEn"),
|
||||
nameDe: requiredText("Category nameDe"),
|
||||
descriptionAr: requiredText("Category descriptionAr"),
|
||||
descriptionEn: requiredText("Category descriptionEn"),
|
||||
descriptionDe: requiredText("Category descriptionDe"),
|
||||
sortOrder: z.coerce.number().int().min(0).max(9999),
|
||||
isActive: z.boolean(),
|
||||
});
|
||||
|
||||
export const sectionInputSchema = z.object({
|
||||
id: z.string().trim().optional(),
|
||||
type: z.nativeEnum(PortfolioSectionType),
|
||||
titleAr: requiredText("Section titleAr"),
|
||||
titleEn: requiredText("Section titleEn"),
|
||||
titleDe: requiredText("Section titleDe"),
|
||||
bodyAr: requiredText("Section bodyAr"),
|
||||
bodyEn: requiredText("Section bodyEn"),
|
||||
bodyDe: requiredText("Section bodyDe"),
|
||||
imagePath: optionalTrimmedText,
|
||||
media: mediaFieldInputSchema.optional(),
|
||||
linkUrl: optionalTrimmedText.refine(
|
||||
(value) => value === "" || /^https?:\/\//.test(value) || value.startsWith("/"),
|
||||
"Section linkUrl must be an absolute URL or start with /.",
|
||||
),
|
||||
sortOrder: z.coerce.number().int().min(0).max(9999),
|
||||
});
|
||||
|
||||
export const assetInputSchema = z.object({
|
||||
id: z.string().trim().optional(),
|
||||
kind: z.nativeEnum(PortfolioAssetKind),
|
||||
filePath: optionalTrimmedText,
|
||||
fileFieldName: optionalTrimmedText,
|
||||
media: mediaFieldInputSchema.optional(),
|
||||
altAr: requiredText("Asset altAr"),
|
||||
altEn: requiredText("Asset altEn"),
|
||||
altDe: requiredText("Asset altDe"),
|
||||
sortOrder: z.coerce.number().int().min(0).max(9999),
|
||||
});
|
||||
|
||||
export const projectInputSchema = z.object({
|
||||
id: z.string().trim().optional(),
|
||||
categoryId: requiredText("Project categoryId"),
|
||||
slug: requiredText("Project slug")
|
||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Project slug must be lowercase and hyphenated."),
|
||||
titleAr: requiredText("Project titleAr"),
|
||||
titleEn: requiredText("Project titleEn"),
|
||||
titleDe: requiredText("Project titleDe"),
|
||||
summaryAr: requiredText("Project summaryAr"),
|
||||
summaryEn: requiredText("Project summaryEn"),
|
||||
summaryDe: requiredText("Project summaryDe"),
|
||||
clientName: requiredText("Project clientName"),
|
||||
projectYear: z.coerce.number().int().min(2000).max(2100),
|
||||
serviceLabelAr: requiredText("Project serviceLabelAr"),
|
||||
serviceLabelEn: requiredText("Project serviceLabelEn"),
|
||||
serviceLabelDe: requiredText("Project serviceLabelDe"),
|
||||
previewUrl: optionalTrimmedText.refine(
|
||||
(value) => value === "" || /^https?:\/\//.test(value),
|
||||
"Project previewUrl must be an absolute URL.",
|
||||
),
|
||||
currentCoverImagePath: optionalTrimmedText,
|
||||
coverMedia: mediaFieldInputSchema.optional(),
|
||||
sortOrder: z.coerce.number().int().min(0).max(9999),
|
||||
isFeatured: z.boolean(),
|
||||
isPublished: z.boolean(),
|
||||
sections: z.array(sectionInputSchema),
|
||||
assets: z.array(assetInputSchema),
|
||||
});
|
||||
Reference in New Issue
Block a user