Finish admin page

This commit is contained in:
Nông Đức Huy
2026-08-13 23:20:23 +07:00
parent cda5d21d0d
commit 8fd23d162f
107 changed files with 6321 additions and 563 deletions
+1
View File
@@ -20,3 +20,4 @@ export * from './catalog-admin';
export * from './commerce';
export * from './content';
export * from './customers';
export * from './taxonomy-admin';
+75
View File
@@ -0,0 +1,75 @@
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<typeof brandInputSchema>;
export type CategoryInput = z.output<typeof categoryInputSchema>;
export type CollectionInput = z.output<typeof collectionInputSchema>;
export type TaxonomyListQuery = z.output<typeof taxonomyListQuerySchema>;