import { z } from 'zod'; import { LOCALES } from '@sport/types'; import { anyIdSchema, slugSchema } from './common'; import { offsetPageQuerySchema } from './pagination'; /** * Per-locale copy for a taxonomy row. * * A partial record, like products and content: publishing an English-only * brand is legitimate, and demanding every locale would mean inventing * Vietnamese copy before anything could be saved. */ const translationSchema = z.object({ name: z.string().trim().min(1).max(160), slug: slugSchema, description: z.string().trim().max(2000).nullish(), metaTitle: z.string().trim().max(255).nullish(), metaDescription: z.string().trim().max(500).nullish(), }); const baseSchema = { /** The fallback name, used when a locale has no translation. */ name: z.string().trim().min(1).max(160), slug: slugSchema, description: z.string().trim().max(2000).nullish(), isActive: z.boolean().default(true), metaTitle: z.string().trim().max(255).nullish(), metaDescription: z.string().trim().max(500).nullish(), translations: z.partialRecord(z.enum(LOCALES), translationSchema).default({}), }; export const brandInputSchema = z.object({ ...baseSchema, logoId: anyIdSchema.nullish(), }); export const categoryInputSchema = z.object({ ...baseSchema, /** * Null means a root category. The service rejects a parent that is the * category itself or one of its own descendants — that would detach the * subtree from the tree and orphan every product under it. */ parentId: anyIdSchema.nullish(), position: z.int().min(0).max(9999).default(0), imageId: anyIdSchema.nullish(), }); export const collectionInputSchema = z .object({ ...baseSchema, type: z.enum(['MANUAL', 'AUTOMATED']).default('MANUAL'), bannerId: anyIdSchema.nullish(), startsAt: z.iso.datetime().nullish(), endsAt: z.iso.datetime().nullish(), position: z.int().min(0).max(9999).default(0), }) .refine( (value) => !value.startsAt || !value.endsAt || new Date(value.startsAt) < new Date(value.endsAt), { message: 'The end date must be after the start date', path: ['endsAt'] }, ); export const taxonomyListQuerySchema = offsetPageQuerySchema.extend({ q: z.string().trim().max(120).optional(), /** Admin default is "everything"; the storefront only ever sees active rows. */ activeOnly: z.coerce.boolean().optional(), }); export type BrandInput = z.output; export type CategoryInput = z.output; export type CollectionInput = z.output; export type TaxonomyListQuery = z.output;