Cart types progress, add zod & initial schema validator

This commit is contained in:
Catalin Pinte 2022-09-07 11:09:10 +03:00
parent 110977424d
commit 15d11aeba1
27 changed files with 452 additions and 284 deletions

View File

@ -49,16 +49,16 @@ export type CartTypes = {
itemBody: CartItemBody itemBody: CartItemBody
} }
export type CartHooks = Core.CartHooks<CartTypes> export type CartHooks = Core.CartHooks
export type GetCartHook = CartHooks['getCart'] export type GetCartHook = CartHooks['getCart']
export type AddItemHook = CartHooks['addItem'] export type AddItemHook = CartHooks['addItem']
export type UpdateItemHook = CartHooks['updateItem'] export type UpdateItemHook = CartHooks['updateItem']
export type RemoveItemHook = CartHooks['removeItem'] export type RemoveItemHook = CartHooks['removeItem']
export type CartSchema = Core.CartSchema<CartTypes> export type CartSchema = Core.CartSchema
export type CartHandlers = Core.CartHandlers<CartTypes> export type CartHandlers = Core.CartHandlers
export type GetCartHandler = CartHandlers['getCart'] export type GetCartHandler = CartHandlers['getCart']
export type AddItemHandler = CartHandlers['addItem'] export type AddItemHandler = CartHandlers['addItem']

View File

@ -11,11 +11,14 @@ import type { WishlistSchema } from '../types/wishlist'
import type { CheckoutSchema } from '../types/checkout' import type { CheckoutSchema } from '../types/checkout'
import type { CustomerCardSchema } from '../types/customer/card' import type { CustomerCardSchema } from '../types/customer/card'
import type { CustomerAddressSchema } from '../types/customer/address' import type { CustomerAddressSchema } from '../types/customer/address'
import { withSchemaParser } from './utils/with-schema-parser'
import { import {
defaultOperations,
OPERATIONS, OPERATIONS,
AllOperations, AllOperations,
APIOperations, APIOperations,
defaultOperations,
} from './operations' } from './operations'
export type APISchemas = export type APISchemas =
@ -106,7 +109,10 @@ export function getCommerceApi<P extends APIProvider>(
OPERATIONS.forEach((k) => { OPERATIONS.forEach((k) => {
const op = ops[k] const op = ops[k]
if (op) { if (op) {
commerce[k] = op({ commerce }) as AllOperations<P>[typeof k] commerce[k] = withSchemaParser(
k,
op({ commerce })
) as AllOperations<P>[typeof k]
} }
}) })

View File

@ -32,6 +32,15 @@ export const defaultOperations = OPERATIONS.reduce((ops, k) => {
export type AllowedOperations = typeof OPERATIONS[number] 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<P extends APIProvider> = { export type Operations<P extends APIProvider> = {
login: { login: {
<T extends LoginOperation>(opts: { <T extends LoginOperation>(opts: {

View File

@ -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<OperationsData>
) =>
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
}
}

View File

@ -5,7 +5,7 @@ import type { AddItemHook } from '../types/cart'
import type { Provider } from '..' import type { Provider } from '..'
export type UseAddItem< export type UseAddItem<
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook> H extends MutationHook<AddItemHook> = MutationHook<AddItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher

View File

@ -4,9 +4,8 @@ import type { SWRHook, HookFetcherFn } from '../utils/types'
import type { GetCartHook } from '../types/cart' import type { GetCartHook } from '../types/cart'
import { Provider, useCommerce } from '..' import { Provider, useCommerce } from '..'
export type UseCart< export type UseCart<H extends SWRHook<GetCartHook> = SWRHook<GetCartHook>> =
H extends SWRHook<GetCartHook<any>> = SWRHook<GetCartHook> ReturnType<H['useHook']>
> = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<GetCartHook> = async ({ export const fetcher: HookFetcherFn<GetCartHook> = async ({
options, options,

View File

@ -5,7 +5,7 @@ import type { RemoveItemHook } from '../types/cart'
import type { Provider } from '..' import type { Provider } from '..'
export type UseRemoveItem< export type UseRemoveItem<
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook> H extends MutationHook<RemoveItemHook> = MutationHook<RemoveItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher

View File

@ -5,7 +5,7 @@ import type { UpdateItemHook } from '../types/cart'
import type { Provider } from '..' import type { Provider } from '..'
export type UseUpdateItem< export type UseUpdateItem<
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook> H extends MutationHook<UpdateItemHook> = MutationHook<UpdateItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseAddItem< export type UseAddItem<
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook> H extends MutationHook<AddItemHook> = MutationHook<AddItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher

View File

@ -7,7 +7,7 @@ import { useHook, useSWRHook } from '../../utils/use-hook'
import { Provider, useCommerce } from '../..' import { Provider, useCommerce } from '../..'
export type UseAddresses< export type UseAddresses<
H extends SWRHook<GetAddressesHook<any>> = SWRHook<GetAddressesHook> H extends SWRHook<GetAddressesHook> = SWRHook<GetAddressesHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<GetAddressesHook> = async ({ export const fetcher: HookFetcherFn<GetAddressesHook> = async ({

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseRemoveItem< export type UseRemoveItem<
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook> H extends MutationHook<RemoveItemHook> = MutationHook<RemoveItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseUpdateItem< export type UseUpdateItem<
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook> H extends MutationHook<UpdateItemHook> = MutationHook<UpdateItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseAddItem< export type UseAddItem<
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook> H extends MutationHook<AddItemHook> = MutationHook<AddItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher

View File

@ -6,9 +6,8 @@ import Cookies from 'js-cookie'
import { useHook, useSWRHook } from '../../utils/use-hook' import { useHook, useSWRHook } from '../../utils/use-hook'
import { Provider, useCommerce } from '../..' import { Provider, useCommerce } from '../..'
export type UseCards< export type UseCards<H extends SWRHook<GetCardsHook> = SWRHook<GetCardsHook>> =
H extends SWRHook<GetCardsHook<any>> = SWRHook<GetCardsHook> ReturnType<H['useHook']>
> = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<GetCardsHook> = async ({ export const fetcher: HookFetcherFn<GetCardsHook> = async ({
options, options,

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseRemoveItem< export type UseRemoveItem<
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook> H extends MutationHook<RemoveItemHook> = MutationHook<RemoveItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseUpdateItem< export type UseUpdateItem<
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook> H extends MutationHook<UpdateItemHook> = MutationHook<UpdateItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher

View File

@ -0,0 +1,5 @@
import * as product from './product'
export default {
product,
}

View File

@ -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(),
})

View File

@ -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 variants name, or the product's name.
*/
name: string
/**
* The product variants price after all discounts are applied.
*/
price: number
/**
* Product variants 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 * The selected option's id
*/ */
id?: string id?: string
/** /**
* The product options name. */ * The product options name.
*/
name: string name: string
/** /**
* The product options value. */ * The product options value.
*/
value: string value: string
} }
export type ProductVariant = { export interface LineItem {
/** /**
* The variant's id. */ * The line item's id.
*/
id: string id: string
/** /**
* The SKU (stock keeping unit) associated with the product variant. */ * The product variants id.
sku: string */
/**
* The product variants 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 variants price after all discounts are applied. */
price: number
/**
* Product variants 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
variantId: string variantId: string
/**
* The product's id.
*/
productId: string productId: string
/**
* The name of the line item.
*/
name: string name: string
/**
* List of discounts applied to the line item.
*/
quantity: number quantity: number
/**
* List of discounts applied to the line item.
*/
discounts: Discount[] discounts: Discount[]
/** /**
* A human-friendly unique string automatically generated from the products name. */ * A human-friendly unique string automatically generated from the products name.
*/
path: string path: string
/**
* The product variant.
*/
variant: ProductVariant variant: ProductVariant
/**
* List of selected options.
*/
options?: SelectedOption[] options?: SelectedOption[]
} }
// Shopping cart, a.k.a Checkout /**
* Shopping cart, a.k.a Checkout
export type Cart = { */
export interface Cart {
/** /**
* The cart's id. */ * The cart's id.
*/
id: string id: string
/** /**
* ID of the customer to which the cart belongs. */ * ID of the customer to which the cart belongs.
*/
customerId?: string customerId?: string
/** /**
* The email assigned to this cart. */ * The URL of the cart.
*/
url?: string
/**
* The email assigned to this cart.
*/
email?: string email?: string
/** /**
* The date and time when the cart was created. */ * The date and time when the cart was created.
*/
createdAt: string createdAt: string
/** /**
* The currency used for this cart */ * The currency used for this cart */
currency: { code: string } currency: { code: string }
/** /**
* Specifies if taxes are included in the line items. */ * Specifies if taxes are included in the line items.
*/
taxesIncluded: boolean taxesIncluded: boolean
/**
* List of cart line items.
*/
lineItems: LineItem[] lineItems: LineItem[]
/** /**
* The sum of all the prices of all the items in the cart. */ * The sum of all the pricexs of all the items in the cart.
/** * Duties, taxes, shipping and discounts excluded.
* Duties, taxes, shipping and discounts excluded. */ */
lineItemsSubtotalPrice: number lineItemsSubtotalPrice: number
/** /**
* Price of the cart before duties, shipping and taxes.*/ * Price of the cart before duties, shipping and taxes.*/
@ -120,93 +147,119 @@ export type Cart = {
/** /**
* Base cart item body used for cart mutations * Base cart item body used for cart mutations
*/ */
export type CartItemBody = { export interface CartItemBody {
/**
* The product variant's id.
*/
variantId: string variantId: string
/**
* The product's id.
*/
productId?: string productId?: string
/**
* The quantity of the product variant.
*/
quantity?: number quantity?: number
} }
/** /**
* Hooks schema * Hooks for add, update & remove items from the cart.
*/ */
export type CartHooks = {
export type CartTypes = { getCart: GetCartHook
cart?: Cart addItem: AddItemHook
item: LineItem updateItem: UpdateItemHook
itemBody: CartItemBody removeItem: RemoveItemHook
} }
export type CartHooks<T extends CartTypes = CartTypes> = { /**
getCart: GetCartHook<T> * Hook for getting the cart.
addItem: AddItemHook<T> */
updateItem: UpdateItemHook<T> export interface GetCartHook {
removeItem: RemoveItemHook<T> data: Cart | null
}
export type GetCartHook<T extends CartTypes = CartTypes> = {
data: T['cart'] | null
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
} }
export type AddItemHook<T extends CartTypes = CartTypes> = { /**
data: T['cart'] * Hook for adding an item to the cart.
input?: T['itemBody'] */
fetcherInput: T['itemBody'] export interface AddItemHook {
body: { item: T['itemBody'] } data: Cart
actionInput: T['itemBody'] input?: CartItemBody
fetcherInput: CartItemBody
body: { item: CartItemBody }
actionInput: CartItemBody
} }
export type UpdateItemHook<T extends CartTypes = CartTypes> = { /**
data: T['cart'] | null * Hook for updating an item in the cart.
input: { item?: T['item']; wait?: number } */
fetcherInput: { itemId: string; item: T['itemBody'] } export interface UpdateItemHook {
body: { itemId: string; item: T['itemBody'] } data: Cart | null | undefined
actionInput: T['itemBody'] & { id: string } input: { item?: LineItem; wait?: number }
fetcherInput: { itemId: string; item: CartItemBody }
body: { itemId: string; item: CartItemBody }
actionInput: CartItemBody & { id: string }
} }
export type RemoveItemHook<T extends CartTypes = CartTypes> = { /**
data: T['cart'] | null * Hook for removing an item from the cart.
input: { item?: T['item'] } */
export interface RemoveItemHook {
data: Cart | null | undefined
input: { item?: LineItem }
fetcherInput: { itemId: string } fetcherInput: { itemId: string }
body: { itemId: string } body: { itemId: string }
actionInput: { id: string } actionInput: { id: string }
} }
/** /**
* API Schema * Cart API Schema.
*/ */
export type CartSchema = {
export type CartSchema<T extends CartTypes = CartTypes> = {
endpoint: { endpoint: {
options: {} options: {}
handlers: CartHandlers<T> handlers: CartHandlers
} }
} }
export type CartHandlers<T extends CartTypes = CartTypes> = { /**
getCart: GetCartHandler<T> * API Handlers for adding, updating & removing items from the cart.
addItem: AddItemHandler<T> */
updateItem: UpdateItemHandler<T> export type CartHandlers = {
removeItem: RemoveItemHandler<T> getCart: GetCartHandler
addItem: AddItemHandler
updateItem: UpdateItemHandler
removeItem: RemoveItemHandler
} }
export type GetCartHandler<T extends CartTypes = CartTypes> = GetCartHook<T> & { /**
* API Handler for getting the cart.
*/
export type GetCartHandler = GetCartHook & {
body: { cartId?: string } body: { cartId?: string }
} }
export type AddItemHandler<T extends CartTypes = CartTypes> = AddItemHook<T> & { /**
* API Handler for adding an item to the cart.
*/
export type AddItemHandler = AddItemHook & {
body: { cartId: string } body: { cartId: string }
} }
export type UpdateItemHandler<T extends CartTypes = CartTypes> = /**
UpdateItemHook<T> & { * API Handler for updating an item in the cart.
data: T['cart'] */
body: { cartId: string } export type UpdateItemHandler = UpdateItemHook & {
} data: Cart
body: { cartId: string }
}
export type RemoveItemHandler<T extends CartTypes = CartTypes> = /**
RemoveItemHook<T> & { * API Handler for removing an item from the cart.
body: { cartId: string } */
} export type RemoveItemHandler = RemoveItemHook & {
body: { cartId: string }
}

View File

@ -1,16 +1,36 @@
export type Discount = { export interface Discount {
// The value of the discount, can be an amount or percentage /**
* The value of the discount, can be an amount or percentage.
*/
value: number value: number
} }
export type Measurement = { export interface Measurement {
/**
* The measurement's value.
*/
value: number value: number
/**
* The measurement's unit.
*/
unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES' unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES'
} }
export type Image = { export interface Image {
/**
* The URL of the image.
*/
url: string url: string
altText?: string /**
* A word or phrase that describes the content of an image.
*/
alt?: string
/**
* The image's width.
*/
width?: number width?: number
/**
* The image's height.
*/
height?: number height?: number
} }

View File

@ -15,97 +15,71 @@ export interface AddressFields {
country: string country: string
} }
export type CustomerAddressTypes = { export type GetAddressesHook = {
address?: Address data: Address[] | null
fields: AddressFields
}
export type GetAddressesHook<
T extends CustomerAddressTypes = CustomerAddressTypes
> = {
data: T['address'][] | null
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
} }
export type AddItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> = export type AddItemHook = {
{ data: Address
data: T['address'] input?: AddressFields
input?: T['fields'] fetcherInput: AddressFields
fetcherInput: T['fields'] body: { item: AddressFields }
body: { item: T['fields'] } actionInput: AddressFields
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 RemoveItemHook< export type UpdateItemHook = {
T extends CustomerAddressTypes = CustomerAddressTypes data: Address | null
> = { input: { item?: AddressFields; wait?: number }
data: T['address'] | null fetcherInput: { itemId: string; item: AddressFields }
input: { item?: T['address'] } body: { itemId: string; item: AddressFields }
actionInput: AddressFields & { id: string }
}
export type RemoveItemHook = {
data: Address | null
input: { item?: Address }
fetcherInput: { itemId: string } fetcherInput: { itemId: string }
body: { itemId: string } body: { itemId: string }
actionInput: { id: string } actionInput: { id: string }
} }
export type CustomerAddressHooks< export type CustomerAddressHooks = {
T extends CustomerAddressTypes = CustomerAddressTypes getAddresses: GetAddressesHook
> = { addItem: AddItemHook
getAddresses: GetAddressesHook<T> updateItem: UpdateItemHook
addItem: AddItemHook<T> removeItem: RemoveItemHook
updateItem: UpdateItemHook<T>
removeItem: RemoveItemHook<T>
} }
export type AddressHandler< export type AddressHandler = GetAddressesHook & {
T extends CustomerAddressTypes = CustomerAddressTypes
> = GetAddressesHook<T> & {
body: { cartId?: string } body: { cartId?: string }
} }
export type AddItemHandler< export type AddItemHandler = AddItemHook & {
T extends CustomerAddressTypes = CustomerAddressTypes
> = AddItemHook<T> & {
body: { cartId: string } body: { cartId: string }
} }
export type UpdateItemHandler< export type UpdateItemHandler = UpdateItemHook & {
T extends CustomerAddressTypes = CustomerAddressTypes data: Address
> = UpdateItemHook<T> & {
data: T['address']
body: { cartId: string } body: { cartId: string }
} }
export type RemoveItemHandler< export type RemoveItemHandler = RemoveItemHook & {
T extends CustomerAddressTypes = CustomerAddressTypes
> = RemoveItemHook<T> & {
body: { cartId: string } body: { cartId: string }
} }
export type CustomerAddressHandlers< export type CustomerAddressHandlers = {
T extends CustomerAddressTypes = CustomerAddressTypes getAddresses: GetAddressesHook
> = { addItem: AddItemHandler
getAddresses: GetAddressesHook<T> updateItem: UpdateItemHandler
addItem: AddItemHandler<T> removeItem: RemoveItemHandler
updateItem: UpdateItemHandler<T>
removeItem: RemoveItemHandler<T>
} }
export type CustomerAddressSchema< export type CustomerAddressSchema = {
T extends CustomerAddressTypes = CustomerAddressTypes
> = {
endpoint: { endpoint: {
options: {} options: {}
handlers: CustomerAddressHandlers<T> handlers: CustomerAddressHandlers
} }
} }

View File

@ -1,99 +1,134 @@
export interface ProductImage { import { Image } from './common'
/**
* The URL of the product image.
*/
url: string
/**
* A word or phrase that describes the content of an image.
*/
alt?: string
}
export interface ProductPrice { export interface ProductPrice {
/** /**
* Decimal price amount. */ * The price after all discounts are applied.
*/
value: number value: number
/** /**
* Currency of the product price. */ * Currency code of the product price.
*/
currencyCode?: 'USD' | 'EUR' | 'ARS' | 'GBP' | string 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 retailPrice?: number
extendedSalePrice?: number
extendedListPrice?: number
} }
export interface ProductOption { export interface ProductOption {
__typename?: 'MultipleChoiceOption' __typename?: 'MultipleChoiceOption'
/** /**
* The option's id. */ * The option's id.
*/
id: string id: string
/** /**
* The option's name. */ * The option's name.
*/
displayName: string displayName: string
/** /**
* List of option values. */ * List of option values.
*/
values: ProductOptionValues[] values: ProductOptionValues[]
} }
export interface ProductOptionValues { export interface ProductOptionValues {
/** /**
* A string that uniquely identifies the option value. */ * A string that uniquely identifies the option value.
*/
label: string 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[] hexColors?: string[]
} }
export interface ProductVariant { export interface ProductVariant {
/** /**
* The variant's id. */ * The variant's id.
*/
id: string | number id: string | number
/** /**
* List of product options. */ * The SKU (stock keeping unit) associated with the product variant.
*/
sku: string
/**
* The product variants name, or the product's name.
*/
name: string
/**
* List of product options.
*/
options: ProductOption[] options: ProductOption[]
/** /**
* Indicates if the variant is available for sale. */ * The product variants price after all discounts are applied.
*/
price: ProductPrice
/**
* Product variants price, as quoted by the manufacturer/distributor.
*/
listPrice: ProductPrice
/**
* Indicates if the variant is available for sale.
*/
availableForSale?: boolean 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 { export interface Product {
/** /**
* The product's id. */ * The product's id.
*/
id: string id: string
/** /**
* The product's name. */ * The product's name.
*/
name: string name: string
/** /**
* Stripped description of the product, single line. */ * Stripped description of the product, single line.
*/
description: string description: string
/** /**
* The description of the product, complete with HTML formatting. */ * The description of the product, complete with HTML formatting.
*/
descriptionHtml?: string descriptionHtml?: string
/** /**
* The SKU (stock keeping unit) associated with the product. * The SKU (stock keeping unit) associated with the product.
*/ */
sku?: string 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 slug?: string
/** /**
* A human-friendly string for the product, containing U. */ * A human-friendly string for the product, containing U.
*/
path?: string path?: string
/** /**
* List of images associated with the product. */ * List of images associated with the product.
images: ProductImage[] */
images: Image[]
/** /**
* List of the products variants. */ * List of the products variants.
*/
variants: ProductVariant[] 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 price: ProductPrice
/** /**
* The product's price. */ * The product's price.
*/
options: ProductOption[] options: ProductOption[]
/** /**
* The products vendor name. */ * The products vendor name.
*/
vendor?: string vendor?: string
} }
@ -123,19 +158,22 @@ export interface SearchProductsBody {
export interface SearchProductsHook { export interface SearchProductsHook {
data: { data: {
/** /**
* List of products matching the query. */ * List of products matching the query.
*/
products: Product[] products: Product[]
/** /**
* Indicates if there are any products matching the query. */ * Indicates if there are any products matching the query.
*/
found: boolean found: boolean
} }
body: SearchProductsBody body: SearchProductsBody
/**
* Indicates if the request is loading. */
input: SearchProductsBody input: SearchProductsBody
fetcherInput: SearchProductsBody fetcherInput: SearchProductsBody
} }
/**
* Product API schema
*/
export interface ProductsSchema { export interface ProductsSchema {
endpoint: { endpoint: {
options: {} options: {}
@ -161,7 +199,8 @@ export interface GetAllProductsOperation {
export interface GetProductOperation { export interface GetProductOperation {
/** /**
* Returned data from the operation. */ * Returned data from the operation.
*/
data: { product?: Product } data: { product?: Product }
/** /**
* The variables to pass to the operation.*/ * The variables to pass to the operation.*/

View File

@ -13,18 +13,16 @@ export type Cart = Core.Cart & {
url?: string url?: string
} }
export type CartTypes = Core.CartTypes export type CartHooks = Core.CartHooks
export type CartHooks = Core.CartHooks<CartTypes>
export type GetCartHook = CartHooks['getCart'] export type GetCartHook = CartHooks['getCart']
export type AddItemHook = CartHooks['addItem'] export type AddItemHook = CartHooks['addItem']
export type UpdateItemHook = CartHooks['updateItem'] export type UpdateItemHook = CartHooks['updateItem']
export type RemoveItemHook = CartHooks['removeItem'] export type RemoveItemHook = CartHooks['removeItem']
export type CartSchema = Core.CartSchema<CartTypes> export type CartSchema = Core.CartSchema
export type CartHandlers = Core.CartHandlers<CartTypes> export type CartHandlers = Core.CartHandlers
export type GetCartHandler = CartHandlers['getCart'] export type GetCartHandler = CartHandlers['getCart']
export type AddItemHandler = CartHandlers['addItem'] export type AddItemHandler = CartHandlers['addItem']

View File

@ -2,7 +2,7 @@ import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types' import type { MutationHook } from '@vercel/commerce/utils/types'
import { CommerceError } from '@vercel/commerce/utils/errors' import { CommerceError } from '@vercel/commerce/utils/errors'
import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item' 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 useCart from './use-cart'
import { import {

View File

@ -1,32 +1 @@
import * as Core from '@vercel/commerce/types/cart'
export * 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<CartTypes>
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<CartTypes>
export type CartHandlers = Core.CartHandlers<CartTypes>
export type GetCartHandler = CartHandlers['getCart']
export type AddItemHandler = CartHandlers['addItem']
export type UpdateItemHandler = CartHandlers['updateItem']
export type RemoveItemHandler = CartHandlers['removeItem']

View File

@ -93,7 +93,7 @@ const CartItem = ({
width={150} width={150}
height={150} height={150}
src={item.variant.image?.url || placeholderImg} src={item.variant.image?.url || placeholderImg}
alt={item.variant.image?.altText || "Product Image"} alt={item.variant.image?.alt || 'Product Image'}
unoptimized unoptimized
/> />
</a> </a>

View File

@ -45,7 +45,8 @@
"react-merge-refs": "^2.0.1", "react-merge-refs": "^2.0.1",
"react-use-measure": "^2.1.1", "react-use-measure": "^2.1.1",
"tabbable": "^5.2.1", "tabbable": "^5.2.1",
"tailwindcss": "^3.0.13" "tailwindcss": "^3.0.13",
"zod": "^3.19.0"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^12.0.8", "@next/bundle-analyzer": "^12.0.8",