diff --git a/packages/bigcommerce/src/types/cart.ts b/packages/bigcommerce/src/types/cart.ts index 3b5d8288b..679733557 100644 --- a/packages/bigcommerce/src/types/cart.ts +++ b/packages/bigcommerce/src/types/cart.ts @@ -49,16 +49,16 @@ export type CartTypes = { itemBody: CartItemBody } -export type CartHooks = Core.CartHooks +export type CartHooks = Core.CartHooks export type GetCartHook = CartHooks['getCart'] export type AddItemHook = CartHooks['addItem'] export type UpdateItemHook = CartHooks['updateItem'] export type RemoveItemHook = CartHooks['removeItem'] -export type CartSchema = Core.CartSchema +export type CartSchema = Core.CartSchema -export type CartHandlers = Core.CartHandlers +export type CartHandlers = Core.CartHandlers export type GetCartHandler = CartHandlers['getCart'] export type AddItemHandler = CartHandlers['addItem'] diff --git a/packages/commerce/src/api/index.ts b/packages/commerce/src/api/index.ts index 6914b9364..c7a46a924 100644 --- a/packages/commerce/src/api/index.ts +++ b/packages/commerce/src/api/index.ts @@ -11,11 +11,14 @@ import type { WishlistSchema } from '../types/wishlist' import type { CheckoutSchema } from '../types/checkout' import type { CustomerCardSchema } from '../types/customer/card' import type { CustomerAddressSchema } from '../types/customer/address' + +import { withSchemaParser } from './utils/with-schema-parser' + import { - defaultOperations, OPERATIONS, AllOperations, APIOperations, + defaultOperations, } from './operations' export type APISchemas = @@ -106,7 +109,10 @@ export function getCommerceApi

( OPERATIONS.forEach((k) => { const op = ops[k] if (op) { - commerce[k] = op({ commerce }) as AllOperations

[typeof k] + commerce[k] = withSchemaParser( + k, + op({ commerce }) + ) as AllOperations

[typeof k] } }) diff --git a/packages/commerce/src/api/operations.ts b/packages/commerce/src/api/operations.ts index 2910a2d82..8798b1455 100644 --- a/packages/commerce/src/api/operations.ts +++ b/packages/commerce/src/api/operations.ts @@ -32,6 +32,15 @@ export const defaultOperations = OPERATIONS.reduce((ops, k) => { export type AllowedOperations = typeof OPERATIONS[number] +export type OperationsData = GetProductOperation['data'] & + GetAllProductsOperation['data'] & + GetAllProductPathsOperation['data'] & + GetCustomerWishlistOperation['data'] & + GetSiteInfoOperation['data'] & + GetPageOperation['data'] & + GetAllPagesOperation['data'] & + LoginOperation['data'] + export type Operations

= { login: { (opts: { diff --git a/packages/commerce/src/api/utils/with-schema-parser.ts b/packages/commerce/src/api/utils/with-schema-parser.ts new file mode 100644 index 000000000..49b090e2f --- /dev/null +++ b/packages/commerce/src/api/utils/with-schema-parser.ts @@ -0,0 +1,48 @@ +import { z } from 'zod' + +import { productSchema } from '../../schemas/product' +import { CommerceError } from '../../utils/errors' + +import type { AllowedOperations, OperationsData } from '../operations' + +export const withSchemaParser = + ( + operation: AllowedOperations, + fn: (...args: any[]) => Promise + ) => + async (...args: any[]) => { + try { + const result = await fn(...args) + parse(operation, result) + return result + } catch (error) { + if (error instanceof z.ZodError) { + throw new CommerceError({ + code: 'SCHEMA_VALIDATION_ERROR', + message: + `The ${operation} opration returned invalid data: \n` + + error.issues + .map((e) => `- ${e.path.join('.')}: ${e.message}`) + .join('\n'), + }) + } else { + throw error + } + } + } + +const parse = (operation: AllowedOperations, data: OperationsData) => { + switch (operation) { + case 'getProduct': + productSchema.parse(data.product) + break + + case 'getAllProducts': + data.products?.forEach((product: any) => productSchema.parse(product)) + break + + case 'getAllProductPaths': + data.products?.forEach((p) => z.string().parse(p.path)) + break + } +} diff --git a/packages/commerce/src/cart/use-add-item.tsx b/packages/commerce/src/cart/use-add-item.tsx index f4072c763..072e68674 100644 --- a/packages/commerce/src/cart/use-add-item.tsx +++ b/packages/commerce/src/cart/use-add-item.tsx @@ -5,7 +5,7 @@ import type { AddItemHook } from '../types/cart' import type { Provider } from '..' export type UseAddItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/cart/use-cart.tsx b/packages/commerce/src/cart/use-cart.tsx index cfce59e36..e37b9844c 100644 --- a/packages/commerce/src/cart/use-cart.tsx +++ b/packages/commerce/src/cart/use-cart.tsx @@ -4,9 +4,8 @@ import type { SWRHook, HookFetcherFn } from '../utils/types' import type { GetCartHook } from '../types/cart' import { Provider, useCommerce } from '..' -export type UseCart< - H extends SWRHook> = SWRHook -> = ReturnType +export type UseCart = SWRHook> = + ReturnType export const fetcher: HookFetcherFn = async ({ options, diff --git a/packages/commerce/src/cart/use-remove-item.tsx b/packages/commerce/src/cart/use-remove-item.tsx index f2bb43ffb..a401f5e10 100644 --- a/packages/commerce/src/cart/use-remove-item.tsx +++ b/packages/commerce/src/cart/use-remove-item.tsx @@ -5,7 +5,7 @@ import type { RemoveItemHook } from '../types/cart' import type { Provider } from '..' export type UseRemoveItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/cart/use-update-item.tsx b/packages/commerce/src/cart/use-update-item.tsx index 2527732eb..099a0b129 100644 --- a/packages/commerce/src/cart/use-update-item.tsx +++ b/packages/commerce/src/cart/use-update-item.tsx @@ -5,7 +5,7 @@ import type { UpdateItemHook } from '../types/cart' import type { Provider } from '..' export type UseUpdateItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/customer/address/use-add-item.tsx b/packages/commerce/src/customer/address/use-add-item.tsx index 94c45142e..664ab06fc 100644 --- a/packages/commerce/src/customer/address/use-add-item.tsx +++ b/packages/commerce/src/customer/address/use-add-item.tsx @@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook' import { mutationFetcher } from '../../utils/default-fetcher' export type UseAddItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/customer/address/use-addresses.tsx b/packages/commerce/src/customer/address/use-addresses.tsx index 7fc12924c..5d6025503 100644 --- a/packages/commerce/src/customer/address/use-addresses.tsx +++ b/packages/commerce/src/customer/address/use-addresses.tsx @@ -7,7 +7,7 @@ import { useHook, useSWRHook } from '../../utils/use-hook' import { Provider, useCommerce } from '../..' export type UseAddresses< - H extends SWRHook> = SWRHook + H extends SWRHook = SWRHook > = ReturnType export const fetcher: HookFetcherFn = async ({ diff --git a/packages/commerce/src/customer/address/use-remove-item.tsx b/packages/commerce/src/customer/address/use-remove-item.tsx index 820a65dad..e284cdd30 100644 --- a/packages/commerce/src/customer/address/use-remove-item.tsx +++ b/packages/commerce/src/customer/address/use-remove-item.tsx @@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook' import { mutationFetcher } from '../../utils/default-fetcher' export type UseRemoveItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/customer/address/use-update-item.tsx b/packages/commerce/src/customer/address/use-update-item.tsx index d05882296..74803f30d 100644 --- a/packages/commerce/src/customer/address/use-update-item.tsx +++ b/packages/commerce/src/customer/address/use-update-item.tsx @@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook' import { mutationFetcher } from '../../utils/default-fetcher' export type UseUpdateItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/customer/card/use-add-item.tsx b/packages/commerce/src/customer/card/use-add-item.tsx index 7b4ffdb17..a3983988a 100644 --- a/packages/commerce/src/customer/card/use-add-item.tsx +++ b/packages/commerce/src/customer/card/use-add-item.tsx @@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook' import { mutationFetcher } from '../../utils/default-fetcher' export type UseAddItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/customer/card/use-cards.tsx b/packages/commerce/src/customer/card/use-cards.tsx index 57099504f..53562c8ec 100644 --- a/packages/commerce/src/customer/card/use-cards.tsx +++ b/packages/commerce/src/customer/card/use-cards.tsx @@ -6,9 +6,8 @@ import Cookies from 'js-cookie' import { useHook, useSWRHook } from '../../utils/use-hook' import { Provider, useCommerce } from '../..' -export type UseCards< - H extends SWRHook> = SWRHook -> = ReturnType +export type UseCards = SWRHook> = + ReturnType export const fetcher: HookFetcherFn = async ({ options, diff --git a/packages/commerce/src/customer/card/use-remove-item.tsx b/packages/commerce/src/customer/card/use-remove-item.tsx index 1d85fa636..5b1539f27 100644 --- a/packages/commerce/src/customer/card/use-remove-item.tsx +++ b/packages/commerce/src/customer/card/use-remove-item.tsx @@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook' import { mutationFetcher } from '../../utils/default-fetcher' export type UseRemoveItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/customer/card/use-update-item.tsx b/packages/commerce/src/customer/card/use-update-item.tsx index cd8837d7f..0542e5dc9 100644 --- a/packages/commerce/src/customer/card/use-update-item.tsx +++ b/packages/commerce/src/customer/card/use-update-item.tsx @@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook' import { mutationFetcher } from '../../utils/default-fetcher' export type UseUpdateItem< - H extends MutationHook> = MutationHook + H extends MutationHook = MutationHook > = ReturnType export const fetcher: HookFetcherFn = mutationFetcher diff --git a/packages/commerce/src/schemas/index.ts b/packages/commerce/src/schemas/index.ts new file mode 100644 index 000000000..2a1dc86cc --- /dev/null +++ b/packages/commerce/src/schemas/index.ts @@ -0,0 +1,5 @@ +import * as product from './product' + +export default { + product, +} diff --git a/packages/commerce/src/schemas/product.ts b/packages/commerce/src/schemas/product.ts new file mode 100644 index 000000000..a3bdc661b --- /dev/null +++ b/packages/commerce/src/schemas/product.ts @@ -0,0 +1,48 @@ +import { z } from 'zod' + +export const productPriceSchema = z.object({ + value: z.number(), + currencyCode: z.string().optional(), + retailPrice: z.number().optional(), +}) + +export const productOptionSchema = z.object({ + id: z.string(), + displayName: z.string(), + values: z.array( + z.object({ + label: z.string(), + hexColors: z.array(z.string()).optional(), + }) + ), +}) + +export const productImageSchema = z.object({ + url: z.string(), + alt: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), +}) + +export const productVariantSchema = z.object({ + id: z.string(), + sku: z.string(), + name: z.string(), + options: z.array(productOptionSchema), + image: productImageSchema.optional(), +}) + +export const productSchema = z.object({ + id: z.string(), + name: z.string(), + description: z.string(), + descriptionHtml: z.string().optional(), + sku: z.string().optional(), + slug: z.string(), + path: z.string().startsWith('/'), + images: z.array(productImageSchema), + variants: z.array(productVariantSchema), + price: productPriceSchema, + options: z.array(productOptionSchema), + vendor: z.string().optional(), +}) diff --git a/packages/commerce/src/types/cart.ts b/packages/commerce/src/types/cart.ts index f76f71d5c..529f1bffc 100644 --- a/packages/commerce/src/types/cart.ts +++ b/packages/commerce/src/types/cart.ts @@ -1,108 +1,135 @@ -import type { Discount, Measurement, Image } from './common' +import type { Discount, Image } from './common' -export type SelectedOption = { +// TODO: This should use the same type as the `ProductVariant` type from `product.ts` +export interface ProductVariant { + /** + * The variant's id. + */ + id: string | number + /** + * The SKU (stock keeping unit) associated with the product variant. + */ + sku: string + /** + * The product variant’s name, or the product's name. + */ + name: string + /** + * The product variant’s price after all discounts are applied. + */ + price: number + /** + * Product variant’s price, as quoted by the manufacturer/distributor. + */ + listPrice: number + /** + * Indicates if the variant is available for sale. + */ + availableForSale?: boolean + /** + * Whether a customer needs to provide a shipping address when placing + * an order for the product variant. + */ + requiresShipping?: boolean + /** + * The image associated with the variant. + */ + image?: Image +} + +export interface SelectedOption { /** * The selected option's id */ id?: string /** - * The product option’s name. */ + * The product option’s name. + */ name: string /** - * The product option’s value. */ + * The product option’s value. + */ value: string } -export type ProductVariant = { +export interface LineItem { /** - * The variant's id. */ + * The line item's id. + */ id: string /** - * The SKU (stock keeping unit) associated with the product variant. */ - sku: string - /** - * The product variant’s title, or the product's name. */ - name: string - /** - * Whether a customer needs to provide a shipping address when placing - * an order for the product variant. */ - requiresShipping: boolean - /** - * The product variant’s price after all discounts are applied. */ - price: number - /** - * Product variant’s price, as quoted by the manufacturer/distributor. */ - listPrice: number - /** - * Image associated with the product variant. Falls back to the product image - * if no image is available. */ - image?: Image - /** - * Indicates whether this product variant is in stock. */ - isInStock?: boolean - /** - * Indicates if the product variant is available for sale. */ - availableForSale?: boolean - /** - * The variant's weight. If a weight was not explicitly specified on the */ - /** - * variant this will be the product's weight. */ - weight?: Measurement - /** - * The variant's height. If a height was not explicitly specified on the - * variant, this will be the product's height. */ - height?: Measurement - /** - * The variant's width. If a width was not explicitly specified on the - - * variant, this will be the product's width. */ - width?: Measurement - /** - * The variant's depth. If a depth was not explicitly specified on the - * variant, this will be the product's depth. */ - depth?: Measurement -} - -export type LineItem = { - id: string + * The product variant’s id. + */ variantId: string + /** + * The product's id. + */ productId: string + /** + * The name of the line item. + */ name: string + /** + * List of discounts applied to the line item. + */ quantity: number + /** + * List of discounts applied to the line item. + */ discounts: Discount[] /** - * A human-friendly unique string automatically generated from the product’s name. */ + * A human-friendly unique string automatically generated from the product’s name. + */ path: string + /** + * The product variant. + */ variant: ProductVariant + /** + * List of selected options. + */ options?: SelectedOption[] } -// Shopping cart, a.k.a Checkout - -export type Cart = { +/** + * Shopping cart, a.k.a Checkout + */ +export interface Cart { /** - * The cart's id. */ + * The cart's id. + */ id: string /** - * ID of the customer to which the cart belongs. */ + * ID of the customer to which the cart belongs. + */ customerId?: string /** - * The email assigned to this cart. */ + * The URL of the cart. + */ + url?: string + /** + * The email assigned to this cart. + */ email?: string /** - * The date and time when the cart was created. */ + * The date and time when the cart was created. + */ createdAt: string /** * The currency used for this cart */ currency: { code: string } /** - * Specifies if taxes are included in the line items. */ + * Specifies if taxes are included in the line items. + */ taxesIncluded: boolean + /** + * List of cart line items. + */ lineItems: LineItem[] /** - * The sum of all the prices of all the items in the cart. */ - /** - * Duties, taxes, shipping and discounts excluded. */ + * The sum of all the pricexs of all the items in the cart. + * Duties, taxes, shipping and discounts excluded. + */ lineItemsSubtotalPrice: number /** * Price of the cart before duties, shipping and taxes.*/ @@ -120,93 +147,119 @@ export type Cart = { /** * Base cart item body used for cart mutations */ -export type CartItemBody = { +export interface CartItemBody { + /** + * The product variant's id. + */ variantId: string + /** + * The product's id. + */ productId?: string + /** + * The quantity of the product variant. + */ quantity?: number } /** - * Hooks schema + * Hooks for add, update & remove items from the cart. */ - -export type CartTypes = { - cart?: Cart - item: LineItem - itemBody: CartItemBody +export type CartHooks = { + getCart: GetCartHook + addItem: AddItemHook + updateItem: UpdateItemHook + removeItem: RemoveItemHook } -export type CartHooks = { - getCart: GetCartHook - addItem: AddItemHook - updateItem: UpdateItemHook - removeItem: RemoveItemHook -} - -export type GetCartHook = { - data: T['cart'] | null +/** + * Hook for getting the cart. + */ +export interface GetCartHook { + data: Cart | null input: {} fetcherInput: { cartId?: string } swrState: { isEmpty: boolean } } -export type AddItemHook = { - data: T['cart'] - input?: T['itemBody'] - fetcherInput: T['itemBody'] - body: { item: T['itemBody'] } - actionInput: T['itemBody'] +/** + * Hook for adding an item to the cart. + */ +export interface AddItemHook { + data: Cart + input?: CartItemBody + fetcherInput: CartItemBody + body: { item: CartItemBody } + actionInput: CartItemBody } -export type UpdateItemHook = { - data: T['cart'] | null - input: { item?: T['item']; wait?: number } - fetcherInput: { itemId: string; item: T['itemBody'] } - body: { itemId: string; item: T['itemBody'] } - actionInput: T['itemBody'] & { id: string } +/** + * Hook for updating an item in the cart. + */ +export interface UpdateItemHook { + data: Cart | null | undefined + input: { item?: LineItem; wait?: number } + fetcherInput: { itemId: string; item: CartItemBody } + body: { itemId: string; item: CartItemBody } + actionInput: CartItemBody & { id: string } } -export type RemoveItemHook = { - data: T['cart'] | null - input: { item?: T['item'] } +/** + * Hook for removing an item from the cart. + */ +export interface RemoveItemHook { + data: Cart | null | undefined + input: { item?: LineItem } fetcherInput: { itemId: string } body: { itemId: string } actionInput: { id: string } } /** - * API Schema + * Cart API Schema. */ - -export type CartSchema = { +export type CartSchema = { endpoint: { options: {} - handlers: CartHandlers + handlers: CartHandlers } } -export type CartHandlers = { - getCart: GetCartHandler - addItem: AddItemHandler - updateItem: UpdateItemHandler - removeItem: RemoveItemHandler +/** + * API Handlers for adding, updating & removing items from the cart. + */ +export type CartHandlers = { + getCart: GetCartHandler + addItem: AddItemHandler + updateItem: UpdateItemHandler + removeItem: RemoveItemHandler } -export type GetCartHandler = GetCartHook & { +/** + * API Handler for getting the cart. + */ +export type GetCartHandler = GetCartHook & { body: { cartId?: string } } -export type AddItemHandler = AddItemHook & { +/** + * API Handler for adding an item to the cart. + */ +export type AddItemHandler = AddItemHook & { body: { cartId: string } } -export type UpdateItemHandler = - UpdateItemHook & { - data: T['cart'] - body: { cartId: string } - } +/** + * API Handler for updating an item in the cart. + */ +export type UpdateItemHandler = UpdateItemHook & { + data: Cart + body: { cartId: string } +} -export type RemoveItemHandler = - RemoveItemHook & { - body: { cartId: string } - } +/** + * API Handler for removing an item from the cart. + */ +export type RemoveItemHandler = RemoveItemHook & { + body: { cartId: string } +} diff --git a/packages/commerce/src/types/common.ts b/packages/commerce/src/types/common.ts index 06908c464..8237ced97 100644 --- a/packages/commerce/src/types/common.ts +++ b/packages/commerce/src/types/common.ts @@ -1,16 +1,36 @@ -export type Discount = { - // The value of the discount, can be an amount or percentage +export interface Discount { + /** + * The value of the discount, can be an amount or percentage. + */ value: number } -export type Measurement = { +export interface Measurement { + /** + * The measurement's value. + */ value: number + /** + * The measurement's unit. + */ unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES' } -export type Image = { +export interface Image { + /** + * The URL of the image. + */ url: string - altText?: string + /** + * A word or phrase that describes the content of an image. + */ + alt?: string + /** + * The image's width. + */ width?: number + /** + * The image's height. + */ height?: number } diff --git a/packages/commerce/src/types/customer/address.ts b/packages/commerce/src/types/customer/address.ts index 8dc6ffc0d..4c738abbb 100644 --- a/packages/commerce/src/types/customer/address.ts +++ b/packages/commerce/src/types/customer/address.ts @@ -15,97 +15,71 @@ export interface AddressFields { country: string } -export type CustomerAddressTypes = { - address?: Address - fields: AddressFields -} - -export type GetAddressesHook< - T extends CustomerAddressTypes = CustomerAddressTypes -> = { - data: T['address'][] | null +export type GetAddressesHook = { + data: Address[] | null input: {} fetcherInput: { cartId?: string } swrState: { isEmpty: boolean } } -export type AddItemHook = - { - data: T['address'] - input?: T['fields'] - fetcherInput: T['fields'] - body: { item: T['fields'] } - actionInput: T['fields'] - } - -export type UpdateItemHook< - T extends CustomerAddressTypes = CustomerAddressTypes -> = { - data: T['address'] | null - input: { item?: T['fields']; wait?: number } - fetcherInput: { itemId: string; item: T['fields'] } - body: { itemId: string; item: T['fields'] } - actionInput: T['fields'] & { id: string } +export type AddItemHook = { + data: Address + input?: AddressFields + fetcherInput: AddressFields + body: { item: AddressFields } + actionInput: AddressFields } -export type RemoveItemHook< - T extends CustomerAddressTypes = CustomerAddressTypes -> = { - data: T['address'] | null - input: { item?: T['address'] } +export type UpdateItemHook = { + data: Address | null + input: { item?: AddressFields; wait?: number } + fetcherInput: { itemId: string; item: AddressFields } + body: { itemId: string; item: AddressFields } + actionInput: AddressFields & { id: string } +} + +export type RemoveItemHook = { + data: Address | null + input: { item?: Address } fetcherInput: { itemId: string } body: { itemId: string } actionInput: { id: string } } -export type CustomerAddressHooks< - T extends CustomerAddressTypes = CustomerAddressTypes -> = { - getAddresses: GetAddressesHook - addItem: AddItemHook - updateItem: UpdateItemHook - removeItem: RemoveItemHook +export type CustomerAddressHooks = { + getAddresses: GetAddressesHook + addItem: AddItemHook + updateItem: UpdateItemHook + removeItem: RemoveItemHook } -export type AddressHandler< - T extends CustomerAddressTypes = CustomerAddressTypes -> = GetAddressesHook & { +export type AddressHandler = GetAddressesHook & { body: { cartId?: string } } -export type AddItemHandler< - T extends CustomerAddressTypes = CustomerAddressTypes -> = AddItemHook & { +export type AddItemHandler = AddItemHook & { body: { cartId: string } } -export type UpdateItemHandler< - T extends CustomerAddressTypes = CustomerAddressTypes -> = UpdateItemHook & { - data: T['address'] +export type UpdateItemHandler = UpdateItemHook & { + data: Address body: { cartId: string } } -export type RemoveItemHandler< - T extends CustomerAddressTypes = CustomerAddressTypes -> = RemoveItemHook & { +export type RemoveItemHandler = RemoveItemHook & { body: { cartId: string } } -export type CustomerAddressHandlers< - T extends CustomerAddressTypes = CustomerAddressTypes -> = { - getAddresses: GetAddressesHook - addItem: AddItemHandler - updateItem: UpdateItemHandler - removeItem: RemoveItemHandler +export type CustomerAddressHandlers = { + getAddresses: GetAddressesHook + addItem: AddItemHandler + updateItem: UpdateItemHandler + removeItem: RemoveItemHandler } -export type CustomerAddressSchema< - T extends CustomerAddressTypes = CustomerAddressTypes -> = { +export type CustomerAddressSchema = { endpoint: { options: {} - handlers: CustomerAddressHandlers + handlers: CustomerAddressHandlers } } diff --git a/packages/commerce/src/types/product.ts b/packages/commerce/src/types/product.ts index 0fa24e7bb..f46dad206 100644 --- a/packages/commerce/src/types/product.ts +++ b/packages/commerce/src/types/product.ts @@ -1,99 +1,134 @@ -export interface ProductImage { - /** - * The URL of the product image. - */ - url: string - /** - * A word or phrase that describes the content of an image. - */ - alt?: string -} +import { Image } from './common' export interface ProductPrice { /** - * Decimal price amount. */ + * The price after all discounts are applied. + */ value: number /** - * Currency of the product price. */ + * Currency code of the product price. + */ currencyCode?: 'USD' | 'EUR' | 'ARS' | 'GBP' | string /** - * The retail price of the product. This can be used to mark a product as on sale, when `retailPrice` is higher than the price a.k.a `value`. */ + * The retail price of the product. This can be used to mark a product as on sale, when `retailPrice` is higher than the price a.k.a `value`. + */ retailPrice?: number - - extendedSalePrice?: number - extendedListPrice?: number } export interface ProductOption { __typename?: 'MultipleChoiceOption' /** - * The option's id. */ + * The option's id. + */ id: string /** - * The option's name. */ + * The option's name. + */ displayName: string /** - * List of option values. */ + * List of option values. + */ values: ProductOptionValues[] } export interface ProductOptionValues { /** - * A string that uniquely identifies the option value. */ + * A string that uniquely identifies the option value. + */ label: string /** - * List of hex colors used to display the actual colors in the swatches instead of the name. */ + * List of hex colors used to display the actual colors in the swatches instead of the name. + */ hexColors?: string[] } + export interface ProductVariant { /** - * The variant's id. */ + * The variant's id. + */ id: string | number /** - * List of product options. */ + * The SKU (stock keeping unit) associated with the product variant. + */ + sku: string + /** + * The product variant’s name, or the product's name. + */ + name: string + /** + * List of product options. + */ options: ProductOption[] /** - * Indicates if the variant is available for sale. */ + * The product variant’s price after all discounts are applied. + */ + price: ProductPrice + /** + * Product variant’s price, as quoted by the manufacturer/distributor. + */ + listPrice: ProductPrice + /** + * Indicates if the variant is available for sale. + */ availableForSale?: boolean + /** + * Whether a customer needs to provide a shipping address when placing an order for the product variant. + */ + requiresShipping?: boolean + /** + * The image associated with the variant. + */ + image?: Image } export interface Product { /** - * The product's id. */ + * The product's id. + */ id: string /** - * The product's name. */ + * The product's name. + */ name: string /** - * Stripped description of the product, single line. */ + * Stripped description of the product, single line. + */ description: string /** - * The description of the product, complete with HTML formatting. */ + * The description of the product, complete with HTML formatting. + */ descriptionHtml?: string /** * The SKU (stock keeping unit) associated with the product. */ sku?: string /** - * A human-friendly unique string for the product, automatically generated from its title. */ + * A human-friendly unique string for the product, automatically generated from its title. + */ slug?: string /** - * A human-friendly string for the product, containing U. */ + * A human-friendly string for the product, containing U. + */ path?: string /** - * List of images associated with the product. */ - images: ProductImage[] + * List of images associated with the product. + */ + images: Image[] /** - * List of the product’s variants. */ + * List of the product’s variants. + */ variants: ProductVariant[] /** - * The product's base price. Could be the minimum value, or default variant price. */ + * The product's base price. Could be the minimum value, or default variant price. + */ price: ProductPrice /** - * The product's price. */ + * The product's price. + */ options: ProductOption[] /** - * The product’s vendor name. */ + * The product’s vendor name. + */ vendor?: string } @@ -123,19 +158,22 @@ export interface SearchProductsBody { export interface SearchProductsHook { data: { /** - * List of products matching the query. */ + * List of products matching the query. + */ products: Product[] /** - * Indicates if there are any products matching the query. */ + * Indicates if there are any products matching the query. + */ found: boolean } body: SearchProductsBody - /** - * Indicates if the request is loading. */ input: SearchProductsBody fetcherInput: SearchProductsBody } +/** + * Product API schema + */ export interface ProductsSchema { endpoint: { options: {} @@ -161,7 +199,8 @@ export interface GetAllProductsOperation { export interface GetProductOperation { /** - * Returned data from the operation. */ + * Returned data from the operation. + */ data: { product?: Product } /** * The variables to pass to the operation.*/ diff --git a/packages/saleor/src/types/cart.ts b/packages/saleor/src/types/cart.ts index 9a4d29387..5774033e1 100644 --- a/packages/saleor/src/types/cart.ts +++ b/packages/saleor/src/types/cart.ts @@ -13,18 +13,16 @@ export type Cart = Core.Cart & { url?: string } -export type CartTypes = Core.CartTypes - -export type CartHooks = Core.CartHooks +export type CartHooks = Core.CartHooks export type GetCartHook = CartHooks['getCart'] export type AddItemHook = CartHooks['addItem'] export type UpdateItemHook = CartHooks['updateItem'] export type RemoveItemHook = CartHooks['removeItem'] -export type CartSchema = Core.CartSchema +export type CartSchema = Core.CartSchema -export type CartHandlers = Core.CartHandlers +export type CartHandlers = Core.CartHandlers export type GetCartHandler = CartHandlers['getCart'] export type AddItemHandler = CartHandlers['addItem'] diff --git a/packages/shopify/src/cart/use-add-item.tsx b/packages/shopify/src/cart/use-add-item.tsx index 5460bd726..b485acfa5 100644 --- a/packages/shopify/src/cart/use-add-item.tsx +++ b/packages/shopify/src/cart/use-add-item.tsx @@ -2,7 +2,7 @@ import { useCallback } from 'react' import type { MutationHook } from '@vercel/commerce/utils/types' import { CommerceError } from '@vercel/commerce/utils/errors' import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item' -import type { AddItemHook } from '../types/cart' +import type { AddItemHook } from '@vercel/commerce/types/cart' import useCart from './use-cart' import { diff --git a/packages/shopify/src/types/cart.ts b/packages/shopify/src/types/cart.ts index 243d369a5..e6838fb45 100644 --- a/packages/shopify/src/types/cart.ts +++ b/packages/shopify/src/types/cart.ts @@ -1,32 +1 @@ -import * as Core from '@vercel/commerce/types/cart' - export * from '@vercel/commerce/types/cart' - -export type ShopifyCart = {} - -/** - * Extend core cart types - */ - -export type Cart = Core.Cart & { - lineItems: Core.LineItem[] - url?: string -} - -export type CartTypes = Core.CartTypes - -export type CartHooks = Core.CartHooks - -export type GetCartHook = CartHooks['getCart'] -export type AddItemHook = CartHooks['addItem'] -export type UpdateItemHook = CartHooks['updateItem'] -export type RemoveItemHook = CartHooks['removeItem'] - -export type CartSchema = Core.CartSchema - -export type CartHandlers = Core.CartHandlers - -export type GetCartHandler = CartHandlers['getCart'] -export type AddItemHandler = CartHandlers['addItem'] -export type UpdateItemHandler = CartHandlers['updateItem'] -export type RemoveItemHandler = CartHandlers['removeItem'] diff --git a/site/components/cart/CartItem/CartItem.tsx b/site/components/cart/CartItem/CartItem.tsx index ecd3e39ae..d76ccbaee 100644 --- a/site/components/cart/CartItem/CartItem.tsx +++ b/site/components/cart/CartItem/CartItem.tsx @@ -93,7 +93,7 @@ const CartItem = ({ width={150} height={150} src={item.variant.image?.url || placeholderImg} - alt={item.variant.image?.altText || "Product Image"} + alt={item.variant.image?.alt || 'Product Image'} unoptimized /> diff --git a/site/package.json b/site/package.json index bdf7688c0..ba838ada4 100644 --- a/site/package.json +++ b/site/package.json @@ -45,7 +45,8 @@ "react-merge-refs": "^2.0.1", "react-use-measure": "^2.1.1", "tabbable": "^5.2.1", - "tailwindcss": "^3.0.13" + "tailwindcss": "^3.0.13", + "zod": "^3.19.0" }, "devDependencies": { "@next/bundle-analyzer": "^12.0.8",