Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!(cms): Sync Order schema from package/types #142

Merged
merged 10 commits into from
Feb 18, 2024
103 changes: 4 additions & 99 deletions apps/cms/src/collections/Orders.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CollectionConfig } from "payload/types";
import Media from "./Media";
import { OrderSchema } from "types";
import { toPayloadZod } from "../utilities/zodInteropt";


/** Orders collection stores merch orders from users. */
const Orders: CollectionConfig = {
Expand All @@ -14,103 +16,6 @@ const Orders: CollectionConfig = {
],
description: "Merchandise orders from users.",
},
fields: [
// by default, payload generates an 'id' field each order automatically
{
name: "paymentGateway",
type: "text",
required: true,
},
{
name: "status",
label: "Order Status",
type: "select",
options: [
{
value: "pending",
label: "Pending Payment",
},
{
value: "paid",
label: "Payment Completed",
},
{
value: "delivered",
label: "Order Completed",
},
],
required: true,
},
{
name: "customerEmail",
type: "email",
required: true,
},
{
name: "transactionID",
label: "Transaction ID",
admin: {
description: "Transaction ID provided by Payment Gateway",
},
type: "text",
required: true,
},
{
name: "orderDateTime",
label: "Ordered On",
type: "date",
admin: {
date: {
pickerAppearance: "dayAndTime",
},
},
required: true,
},
// ordered items for this order
{
name: "orderItems",
type: "array",
fields: [
{
name: "image",
type: "upload",
relationTo: Media.slug,
// validation: only allow image filetypes
filterOptions: {
mimeType: { contains: "image" },
},
},
{
name: "quantity",
type: "number",
required: true,
},
{
name: "size",
type: "text",
required: true,
},
{
name: "price",
type: "number",
required: true,
},
{
name: "name",
type: "text",
required: true,
},
{
name: "colorway",
type: "text",
required: true,
},
],
// direct paylaod to generate a OrderItem type
interfaceName: "OrderItem",
// validate: orders should not be empty
minRows: 1,
},
],
fields: toPayloadZod(OrderSchema),
};
export default Orders;
81 changes: 81 additions & 0 deletions apps/cms/src/utilities/zodInteropt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Field } from "payload/types";
import { z } from "zod";

/**
* Converts Zod field with given type to a corresponding Payload field.
* @param name Name of the Zod field to convert.
* @param zodType Data type of the field to convert.
* @returns Payload Field definition suitable for use in Payload collections.
*/
function toPayloadZodField(
name: string,
zodType: z.ZodFirstPartySchemaTypes
): Field {
const required = zodType.isOptional() ? {} : { required: true };
const field = {
name: name,
...required,
};

// zod types are matched by type name as matching with instanceof breaks bundler
switch (zodType._def.typeName) {
case z.ZodFirstPartyTypeKind.ZodString:
return { ...field, type: "text" };
case z.ZodFirstPartyTypeKind.ZodNumber:
return { ...field, type: "number" };
break;
case z.ZodFirstPartyTypeKind.ZodArray:
return {
...field,
type: "array",
// convert nested type stored in array
fields: toPayloadZod(
(zodType as z.ZodArray<z.ZodObject<z.ZodRawShape>>).element
),
};
case z.ZodFirstPartyTypeKind.ZodNativeEnum:
return {
...field,
type: "select",
// unpack enum entries as select options
// typescript encodes enums are encoded as bidirectional dictionary
// with both entries from option -> value and value -> option
// use zod parsing to select only the options -> value entries
options: Object.entries((zodType as z.ZodNativeEnum<z.EnumLike>).enum)
.filter(([_, right]) => zodType.safeParse(right).success)
.map(([option, value]) => {
return { label: option, value: `${value}` };
}),
};

case z.ZodFirstPartyTypeKind.ZodOptional:
return {
...toPayloadZodField(
name,
(zodType as z.ZodOptional<z.ZodTypeAny>).unwrap()
),
// override nested field required true with false
...field,
};

default:
throw new Error(
`Unable to convert unsupported Zod type: ${JSON.stringify(
zodType,
null,
2
)}`
);
}
}

/**
* Converts Zod Object into Payload field definitions.
* @param zodObject Zod Object to convert.
* @returns List of Payload fields corresponding to the fields of the Zod object.
*/
export function toPayloadZod(zodObject: z.ZodObject<z.ZodRawShape>): Field[] {
return Object.entries(zodObject.shape).map(([name, zodType]) =>
toPayloadZodField(name, zodType)
);
}
3 changes: 2 additions & 1 deletion apps/cms/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
"jsx": "react",
"allowSyntheticDefaultImports": true,
"paths": {
// Ensure this matches the path to your typescript outputFile
"payload/generated-types": [
"../../packages/types/lib/cms.ts" // Ensure this matches the path to your typescript outputFile
"../../packages/types/src/index.ts"
]
}
},
Expand Down
4 changes: 1 addition & 3 deletions packages/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
"name": "types",
"version": "0.0.1",
"private": true,
"main": "./dist/index.js",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
"main": "./src/index.ts",
"files": [
"dist/**"
],
Expand Down
30 changes: 14 additions & 16 deletions packages/types/src/lib/cms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@
* and re-run `payload generate:types` to regenerate this file.
*/

export type OrderItem = {
image?: string | Media;
quantity: number;
size: string;
price: number;
name: string;
colorway: string;
id?: string;
}[];

export interface Config {
collections: {
categories: Category;
Expand Down Expand Up @@ -100,12 +90,20 @@ export interface User {
}
export interface Order {
id: string;
paymentGateway: string;
status: 'pending' | 'paid' | 'delivered';
customerEmail: string;
transactionID: string;
orderDateTime: string;
orderItems?: OrderItem;
items: {
id: string;
name: string;
image: string;
color: string;
size: string;
price: number;
quantity: number;
}[];
transaction_id: string;
transaction_time: string;
payment_method: string;
customer_email: string;
status: '1' | '2' | '3';
mrzzy marked this conversation as resolved.
Show resolved Hide resolved
updatedAt: string;
createdAt: string;
}
43 changes: 24 additions & 19 deletions packages/types/src/lib/merch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,36 @@ export interface Product {
}

// Order
// use zod to define order types to allow runtime reflection of type structure.
// by default, typescript types lose type information time at compile time.
export enum OrderStatus {
PENDING_PAYMENT = 1,
PAYMENT_COMPLETED = 2,
ORDER_COMPLETED = 3,
}
const OrderStatusSchema = z.nativeEnum(OrderStatus);

export interface Order {
id: string;
items: OrderItem[];
transaction_id: string;
transaction_time: string | null;
payment_method: string;
customer_email: string;
status: OrderStatus;
}
const OrderItem = z.object({
id: z.string(),
name: z.string(),
image: z.string().optional(),
color: z.string(),
size: z.string(),
price: z.number(),
quantity: z.number(),
});
export type OrderItem = z.infer<typeof OrderItem>;

export const OrderSchema = z.object({
id: z.string(),
items: z.array(OrderItem),
transaction_id: z.string(),
transaction_time: z.string().optional(),
payment_method: z.string(),
customer_email: z.string(),
status: OrderStatusSchema,
});
export type Order = z.infer<typeof OrderSchema>;

// Cart
export type CartState = {
Expand All @@ -58,16 +73,6 @@ export type Cart = z.infer<typeof Cart>;
export type CartItem = z.infer<typeof CartItem>;

// Promotion
export interface OrderItem {
id: string;
name: string;
image?: string;
color: string;
size: string;
price: number;
quantity: number;
}

export interface Promotion {
promoCode: string;
maxRedemptions: number;
Expand Down
Loading