Updated types, schemas & providers

This commit is contained in:
Catalin Pinte 2022-09-12 11:36:59 +03:00
parent 163a22ec88
commit ba0e7beb73
114 changed files with 1006 additions and 1001 deletions

View File

@ -1130,7 +1130,7 @@ export interface definitions {
*/ */
search_keywords?: string search_keywords?: string
/** /**
* Image URL used for this category on the storefront. Images can be uploaded via form file post to `/brands/{brandId}/image`, or by providing a publicly accessible URL in this field. * Image URL used for this category on the storefront. Images can be uploaded via form file post to `/{brandId}/image`, or by providing a publicly accessible URL in this field.
*/ */
image_url?: string image_url?: string
custom_url?: definitions['customUrl_Full'] custom_url?: definitions['customUrl_Full']

View File

@ -1,7 +1,6 @@
import type { Wishlist } from '../../../types/wishlist' import type { Wishlist } from '../../../types/wishlist'
import type { WishlistEndpoint } from '.' import type { WishlistEndpoint } from '.'
import getCustomerId from '../../utils/get-customer-id' import getCustomerId from '../../utils/get-customer-id'
import getCustomerWishlist from '../../operations/get-customer-wishlist'
// Return wishlist info // Return wishlist info
const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({ const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({

View File

@ -49,7 +49,7 @@ export default function getCustomerWishlistOperation({
if (includeProducts && wishlist?.items?.length) { if (includeProducts && wishlist?.items?.length) {
const ids = wishlist.items const ids = wishlist.items
?.map((item) => (item?.product_id ? String(item?.product_id) : null)) ?.map((item) => (item?.productId ? String(item?.productId) : null))
.filter((id): id is string => !!id) .filter((id): id is string => !!id)
if (ids?.length) { if (ids?.length) {
@ -66,7 +66,7 @@ export default function getCustomerWishlistOperation({
}, {}) }, {})
// Populate the wishlist items with the graphql products // Populate the wishlist items with the graphql products
wishlist.items.forEach((item) => { wishlist.items.forEach((item) => {
const product = item && productsById[item.product_id!] const product = item && productsById[Number(item.productId)]
if (item && product) { if (item && product) {
// @ts-ignore Fix this type when the wishlist type is properly defined // @ts-ignore Fix this type when the wishlist type is properly defined
item.product = product item.product = product

View File

@ -7,7 +7,7 @@ import type { GetSiteInfoQuery } from '../../../schema'
import filterEdges from '../utils/filter-edges' import filterEdges from '../utils/filter-edges'
import type { BigcommerceConfig, Provider } from '..' import type { BigcommerceConfig, Provider } from '..'
import { categoryTreeItemFragment } from '../fragments/category-tree' import { categoryTreeItemFragment } from '../fragments/category-tree'
import { normalizeCategory } from '../../lib/normalize' import { normalizeBrand, normalizeCategory } from '../../lib/normalize'
// Get 3 levels of categories // Get 3 levels of categories
export const getSiteInfoQuery = /* GraphQL */ ` export const getSiteInfoQuery = /* GraphQL */ `
@ -79,7 +79,7 @@ export default function getSiteInfoOperation({
return { return {
categories: categories ?? [], categories: categories ?? [],
brands: filterEdges(brands), brands: filterEdges(brands).map(normalizeBrand),
} }
} }

View File

@ -1,5 +1,5 @@
import type { WishlistItemBody } from '../../types/wishlist' import type { WishlistItemBody } from '../../types/wishlist'
import type { CartItemBody, OptionSelections } from '../../types/cart' import type { CartItemBody, SelectedOption } from '../../types/cart'
type BCWishlistItemBody = { type BCWishlistItemBody = {
product_id: number product_id: number
@ -10,7 +10,7 @@ type BCCartItemBody = {
product_id: number product_id: number
variant_id: number variant_id: number
quantity?: number quantity?: number
option_selections?: OptionSelections[] option_selections?: SelectedOption[]
} }
export const parseWishlistItem = ( export const parseWishlistItem = (
@ -24,5 +24,5 @@ export const parseCartItem = (item: CartItemBody): BCCartItemBody => ({
quantity: item.quantity, quantity: item.quantity,
product_id: Number(item.productId), product_id: Number(item.productId),
variant_id: Number(item.variantId), variant_id: Number(item.variantId),
option_selections: item.optionSelections, option_selections: item.optionsSelected,
}) })

View File

@ -1,7 +1,7 @@
import type { Product } from '../types/product' import type { Product } from '../types/product'
import type { Cart, BigcommerceCart, LineItem } from '../types/cart' import type { Cart, BigcommerceCart, LineItem } from '../types/cart'
import type { Page } from '../types/page' import type { Page } from '../types/page'
import type { BCCategory, Category } from '../types/site' import type { BCCategory, BCBrand, Category, Brand } from '../types/site'
import { definitions } from '../api/definitions/store-content' import { definitions } from '../api/definitions/store-content'
import update from './immutability' import update from './immutability'
import getSlug from './get-slug' import getSlug from './get-slug'
@ -54,7 +54,7 @@ export function normalizeProduct(productNode: any): Product {
: [], : [],
}, },
brand: { brand: {
$apply: (brand: any) => (brand?.entityId ? brand?.entityId : null), $apply: (brand: any) => (brand?.id ? brand.id : null),
}, },
slug: { slug: {
$set: path?.replace(/^\/+|\/+$/g, ''), $set: path?.replace(/^\/+|\/+$/g, ''),
@ -134,3 +134,12 @@ export function normalizeCategory(category: BCCategory): Category {
path: category.path, path: category.path,
} }
} }
export function normalizeBrand(brand: BCBrand): Brand {
return {
id: `${brand.node.entityId}`,
name: brand.node.name,
slug: getSlug(brand.node.path),
path: brand.node.path,
}
}

View File

@ -1,5 +1,3 @@
import * as Core from '@vercel/commerce/types/cart'
export * from '@vercel/commerce/types/cart' export * from '@vercel/commerce/types/cart'
// TODO: this type should match: // TODO: this type should match:
@ -24,43 +22,3 @@ export type BigcommerceCart = {
discounts?: { id: number; discounted_amount: number }[] discounts?: { id: number; discounted_amount: number }[]
// TODO: add missing fields // TODO: add missing fields
} }
/**
* Extend core cart types
*/
export type Cart = Core.Cart & {
lineItems: Core.LineItem[]
}
export type OptionSelections = {
option_id: number
option_value: number | string
}
export type CartItemBody = Core.CartItemBody & {
productId: string // The product id is always required for BC
optionSelections?: OptionSelections[]
}
export type CartTypes = {
cart: Cart
item: Core.LineItem
itemBody: CartItemBody
}
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']

View File

@ -1,5 +1 @@
import * as Core from '@vercel/commerce/types/customer'
export * from '@vercel/commerce/types/customer' export * from '@vercel/commerce/types/customer'
export type CustomerSchema = Core.CustomerSchema

View File

@ -1,8 +1 @@
import * as Core from '@vercel/commerce/types/login'
import type { LoginMutationVariables } from '../../schema'
export * from '@vercel/commerce/types/login' export * from '@vercel/commerce/types/login'
export type LoginOperation = Core.LoginOperation & {
variables: LoginMutationVariables
}

View File

@ -1,11 +1 @@
import * as Core from '@vercel/commerce/types/page'
export * from '@vercel/commerce/types/page' export * from '@vercel/commerce/types/page'
export type Page = Core.Page
export type PageTypes = {
page: Page
}
export type GetAllPagesOperation = Core.GetAllPagesOperation<PageTypes>
export type GetPageOperation = Core.GetPageOperation<PageTypes>

View File

@ -1,5 +1,4 @@
import * as Core from '@vercel/commerce/types/site' import type { GetSiteInfoQuery } from '../../schema'
import type { GetSiteInfoQuery, GetSiteInfoQueryVariables } from '../../schema'
export * from '@vercel/commerce/types/site' export * from '@vercel/commerce/types/site'
@ -7,13 +6,6 @@ export type BCCategory = NonNullable<
GetSiteInfoQuery['site']['categoryTree'] GetSiteInfoQuery['site']['categoryTree']
>[0] >[0]
export type Brand = NonNullable< export type BCBrand = NonNullable<
NonNullable<GetSiteInfoQuery['site']['brands']['edges']>[0] NonNullable<GetSiteInfoQuery['site']['brands']['edges']>[0]
> >
export type SiteTypes = {
category: Core.Category
brand: Brand
}
export type GetSiteInfoOperation = Core.GetSiteInfoOperation<SiteTypes>

View File

@ -1,24 +1 @@
import * as Core from '@vercel/commerce/types/wishlist'
import { definitions } from '../api/definitions/wishlist'
import type { ProductEdge } from '../api/operations/get-all-products'
export * from '@vercel/commerce/types/wishlist' export * from '@vercel/commerce/types/wishlist'
export type WishlistItem = NonNullable<
definitions['wishlist_Full']['items']
>[0] & {
product?: ProductEdge['node']
}
export type Wishlist = Omit<definitions['wishlist_Full'], 'items'> & {
items?: WishlistItem[]
}
export type WishlistTypes = {
wishlist: Wishlist
itemBody: Core.WishlistItemBody
}
export type WishlistSchema = Core.WishlistSchema<WishlistTypes>
export type GetCustomerWishlistOperation =
Core.GetCustomerWishlistOperation<WishlistTypes>

View File

@ -3,58 +3,60 @@ import { CommerceAPIError } from '../utils/errors'
import isAllowedOperation from '../utils/is-allowed-operation' import isAllowedOperation from '../utils/is-allowed-operation'
import type { GetAPISchema } from '..' import type { GetAPISchema } from '..'
const cartEndpoint: GetAPISchema<any, CartSchema<any>>['endpoint']['handler'] = const cartEndpoint: GetAPISchema<
async (ctx) => { any,
const { req, res, handlers, config } = ctx CartSchema
>['endpoint']['handler'] = async (ctx) => {
const { req, res, handlers, config } = ctx
if ( if (
!isAllowedOperation(req, res, { !isAllowedOperation(req, res, {
GET: handlers['getCart'], GET: handlers['getCart'],
POST: handlers['addItem'], POST: handlers['addItem'],
PUT: handlers['updateItem'], PUT: handlers['updateItem'],
DELETE: handlers['removeItem'], DELETE: handlers['removeItem'],
}) })
) { ) {
return return
}
const { cookies } = req
const cartId = cookies[config.cartCookie]
try {
// Return current cart info
if (req.method === 'GET') {
const body = { cartId }
return await handlers['getCart']({ ...ctx, body })
}
// Create or add an item to the cart
if (req.method === 'POST') {
const body = { ...req.body, cartId }
return await handlers['addItem']({ ...ctx, body })
}
// Update item in cart
if (req.method === 'PUT') {
const body = { ...req.body, cartId }
return await handlers['updateItem']({ ...ctx, body })
}
// Remove an item from the cart
if (req.method === 'DELETE') {
const body = { ...req.body, cartId }
return await handlers['removeItem']({ ...ctx, body })
}
} catch (error) {
console.error(error)
const message =
error instanceof CommerceAPIError
? 'An unexpected error ocurred with the Commerce API'
: 'An unexpected error ocurred'
res.status(500).json({ data: null, errors: [{ message }] })
}
} }
const { cookies } = req
const cartId = cookies[config.cartCookie]
try {
// Return current cart info
if (req.method === 'GET') {
const body = { cartId }
return await handlers['getCart']({ ...ctx, body })
}
// Create or add an item to the cart
if (req.method === 'POST') {
const body = { ...req.body, cartId }
return await handlers['addItem']({ ...ctx, body })
}
// Update item in cart
if (req.method === 'PUT') {
const body = { ...req.body, cartId }
return await handlers['updateItem']({ ...ctx, body })
}
// Remove an item from the cart
if (req.method === 'DELETE') {
const body = { ...req.body, cartId }
return await handlers['removeItem']({ ...ctx, body })
}
} catch (error) {
console.error(error)
const message =
error instanceof CommerceAPIError
? 'An unexpected error ocurred with the Commerce API'
: 'An unexpected error ocurred'
res.status(500).json({ data: null, errors: [{ message }] })
}
}
export default cartEndpoint export default cartEndpoint

View File

@ -1,5 +1,5 @@
import type { ProductsSchema } from '../../../types/product' import type { ProductsSchema } from '../../../types/product'
import { CommerceAPIError } from '../../utils/errors' import { getErrorMessage } from '../../utils/errors'
import isAllowedOperation from '../../utils/is-allowed-operation' import isAllowedOperation from '../../utils/is-allowed-operation'
import type { GetAPISchema } from '../..' import type { GetAPISchema } from '../..'
@ -18,12 +18,7 @@ const productsEndpoint: GetAPISchema<
return await handlers['getProducts']({ ...ctx, body }) return await handlers['getProducts']({ ...ctx, body })
} catch (error) { } catch (error) {
console.error(error) console.error(error)
const message = getErrorMessage(error)
const message =
error instanceof CommerceAPIError
? 'An unexpected error ocurred with the Commerce API'
: 'An unexpected error ocurred'
res.status(500).json({ data: null, errors: [{ message }] }) res.status(500).json({ data: null, errors: [{ message }] })
} }
} }

View File

@ -1,11 +1,11 @@
import type { LoginSchema } from '../../types/login' import type { LoginSchema } from '../../types/login'
import { CommerceAPIError } from '../utils/errors' import { CommerceAPIError, getErrorMessage } from '../utils/errors'
import isAllowedOperation from '../utils/is-allowed-operation' import isAllowedOperation from '../utils/is-allowed-operation'
import type { GetAPISchema } from '..' import type { GetAPISchema } from '..'
const loginEndpoint: GetAPISchema< const loginEndpoint: GetAPISchema<
any, any,
LoginSchema<any> LoginSchema
>['endpoint']['handler'] = async (ctx) => { >['endpoint']['handler'] = async (ctx) => {
const { req, res, handlers } = ctx const { req, res, handlers } = ctx
@ -24,10 +24,7 @@ const loginEndpoint: GetAPISchema<
} catch (error) { } catch (error) {
console.error(error) console.error(error)
const message = const message = getErrorMessage(error)
error instanceof CommerceAPIError
? 'An unexpected error ocurred with the Commerce API'
: 'An unexpected error ocurred'
res.status(500).json({ data: null, errors: [{ message }] }) res.status(500).json({ data: null, errors: [{ message }] })
} }

View File

@ -5,7 +5,7 @@ import type { GetAPISchema } from '..'
const wishlistEndpoint: GetAPISchema< const wishlistEndpoint: GetAPISchema<
any, any,
WishlistSchema<any> WishlistSchema
>['endpoint']['handler'] = async (ctx) => { >['endpoint']['handler'] = async (ctx) => {
const { req, res, handlers, config } = ctx const { req, res, handlers, config } = ctx

View File

@ -1,4 +1,7 @@
import { ZodError } from 'zod'
import type { Response } from '@vercel/fetch' import type { Response } from '@vercel/fetch'
import { CommerceError } from '../../utils/errors'
export class CommerceAPIError extends Error { export class CommerceAPIError extends Error {
status: number status: number
@ -20,3 +23,38 @@ export class CommerceNetworkError extends Error {
this.name = 'CommerceNetworkError' this.name = 'CommerceNetworkError'
} }
} }
export const getErrorMessage = (error: unknown) => {
return error instanceof CommerceAPIError
? 'An unexpected error ocurred with the Commerce API'
: 'An unexpected error ocurred'
}
export const getOperationError = (operation: string, error: unknown) => {
if (error instanceof ZodError) {
error = new CommerceError({
code: 'SCHEMA_VALIDATION_ERROR',
message:
`The ${operation} operation returned invalid data and has ${
error.issues.length
} parse ${error.issues.length === 1 ? 'error' : 'errors'}: \n` +
error.issues
.map(
(e, index) =>
`Error #${index + 1} ${
e.path.length > 0 ? `Path: ${e.path.join('.')}, ` : ''
}Code: ${e.code}, Message: ${e.message}`
)
.join('\n'),
})
}
if (error instanceof Error) {
return new CommerceError({
code: 'OPERATION_ERROR',
message: `An unexpected error ocurred with the ${operation} operation: ${error.message}`,
})
}
return error
}

View File

@ -1,10 +1,11 @@
import { z } from 'zod'
import { productSchema } from '../../schemas/product'
import { CommerceError } from '../../utils/errors'
import type { AllowedOperations, OperationsData } from '../operations' import type { AllowedOperations, OperationsData } from '../operations'
import { z } from 'zod'
import { getOperationError } from './errors'
import { pageSchema } from '../../schemas/page'
import { siteInfoSchema } from '../../schemas/site'
import { productSchema, productsPathsSchema } from '../../schemas/product'
export const withSchemaParser = export const withSchemaParser =
( (
operation: AllowedOperations, operation: AllowedOperations,
@ -16,27 +17,7 @@ export const withSchemaParser =
parse(operation, result) parse(operation, result)
return result return result
} catch (error) { } catch (error) {
if (error instanceof z.ZodError) { return Promise.reject(getOperationError(operation, error))
return Promise.reject(
new CommerceError({
code: 'SCHEMA_VALIDATION_ERROR',
message:
`The ${operation} opration returned invalid data and has ${
error.issues.length
} parse ${error.issues.length === 1 ? 'error' : 'errors'}: \n` +
error.issues
.map(
(e, index) =>
`${index + 1}. Property ${e.path.join('.')} (${e.code}): ${
e.message
}`
)
.join('\n'),
})
)
} else {
return Promise.reject(error)
}
} }
} }
@ -47,11 +28,26 @@ const parse = (operation: AllowedOperations, data: OperationsData) => {
break break
case 'getAllProducts': case 'getAllProducts':
data.products?.forEach((product: any) => productSchema.parse(product)) z.array(productSchema).parse(data.products)
break break
case 'getAllProductPaths': case 'getAllProductPaths':
data.products?.forEach((p) => z.string().parse(p.path)) productsPathsSchema.parse(data.products)
break
case 'getPage':
pageSchema.parse(data.page)
break
case 'getAllPages':
z.array(pageSchema).parse(data.pages)
break
case 'getSiteInfo':
siteInfoSchema.parse({
categories: data.categories,
brands: data.brands,
})
break break
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,18 @@
import { z } from 'zod'
export const pageSchema = z.object({
id: z.string(),
name: z.string(),
url: z.string().startsWith('/'),
body: z.string(),
is_visible: z.boolean().optional(),
sort_order: z.number().optional(),
})
export const pagesPathsSchema = z.array(
z.object({
page: z.object({
path: z.string(),
}),
})
)

View File

@ -2,7 +2,7 @@ import { z } from 'zod'
export const productPriceSchema = z.object({ export const productPriceSchema = z.object({
value: z.number(), value: z.number(),
currencyCode: z.string().optional(), currencyCode: z.string().max(3).optional(),
retailPrice: z.number().optional(), retailPrice: z.number().optional(),
}) })
@ -18,7 +18,7 @@ export const productOptionSchema = z.object({
}) })
export const productImageSchema = z.object({ export const productImageSchema = z.object({
url: z.string(), url: z.string().url().or(z.string().startsWith('/')),
alt: z.string().optional(), alt: z.string().optional(),
width: z.number().optional(), width: z.number().optional(),
height: z.number().optional(), height: z.number().optional(),
@ -26,7 +26,7 @@ export const productImageSchema = z.object({
export const productVariantSchema = z.object({ export const productVariantSchema = z.object({
id: z.string(), id: z.string(),
sku: z.string(), sku: z.string().optional(),
name: z.string(), name: z.string(),
options: z.array(productOptionSchema), options: z.array(productOptionSchema),
image: productImageSchema.optional(), image: productImageSchema.optional(),
@ -46,3 +46,15 @@ export const productSchema = z.object({
options: z.array(productOptionSchema), options: z.array(productOptionSchema),
vendor: z.string().optional(), vendor: z.string().optional(),
}) })
export const productsPathsSchema = z.array(
z.object({ path: z.string().startsWith('/') })
)
export const searchProductBodySchema = z.object({
search: z.string(),
categoryId: z.string(),
brandId: z.string().optional(),
sort: z.string().optional(),
locale: z.string().optional(),
})

View File

@ -0,0 +1,18 @@
import { z } from 'zod'
export const siteInfoSchema = z.object({
categories: z.array(
z.object({
id: z.string(),
name: z.string(),
path: z.string().startsWith('/'),
})
),
brands: z.array(
z.object({
id: z.string(),
name: z.string(),
path: z.string().startsWith('/'),
})
),
})

View File

@ -3,13 +3,13 @@ import type { Discount, Image } from './common'
// TODO: This should use the same type as the `ProductVariant` type from `product.ts` // TODO: This should use the same type as the `ProductVariant` type from `product.ts`
export interface ProductVariant { export interface ProductVariant {
/** /**
* The variant's id. * The unique identifier for the variant.
*/ */
id: string | number id: string
/** /**
* The SKU (stock keeping unit) associated with the product variant. * The SKU (stock keeping unit) associated with the product variant.
*/ */
sku: string sku?: string
/** /**
* The product variants name, or the product's name. * The product variants name, or the product's name.
*/ */
@ -19,7 +19,7 @@ export interface ProductVariant {
*/ */
price: number price: number
/** /**
* Product variants price, as quoted by the manufacturer/distributor. * The product variants price before discounts are applied.
*/ */
listPrice: number listPrice: number
/** /**
@ -39,38 +39,38 @@ export interface ProductVariant {
export interface SelectedOption { export interface SelectedOption {
/** /**
* The selected option's id * The unique identifier for the option.
*/ */
id?: string id?: string
/** /**
* The product options name. * The product options name, such as "Color" or "Size".
*/ */
name: string name: string
/** /**
* The product options value. * The product options value, such as "Red" or "XL".
*/ */
value: string value: string
} }
export interface LineItem { export interface LineItem {
/** /**
* The line item's id. * The unique identifier for the line item.
*/ */
id: string id: string
/** /**
* The product variants id. * The unique identifier for the product variant.
*/ */
variantId: string variantId: string
/** /**
* The product's id. * The unique identifier for the product, if the variant is not provided.
*/ */
productId: string productId: string
/** /**
* The name of the line item. * This is usually the product's name.
*/ */
name: string name: string
/** /**
* List of discounts applied to the line item. * The quantity of the product variant in the line item.
*/ */
quantity: number quantity: number
/** /**
@ -86,7 +86,7 @@ export interface LineItem {
*/ */
variant: ProductVariant variant: ProductVariant
/** /**
* List of selected options. * List of selected options, to be used when displaying the line item, such as Color: Red, Size: XL.
*/ */
options?: SelectedOption[] options?: SelectedOption[]
} }
@ -96,7 +96,7 @@ export interface LineItem {
*/ */
export interface Cart { export interface Cart {
/** /**
* The cart's id. * The unique identifier for the cart.
*/ */
id: string id: string
/** /**
@ -119,7 +119,7 @@ export interface Cart {
* 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. * Indicates if taxes are included in the line items.
*/ */
taxesIncluded: boolean taxesIncluded: boolean
/** /**
@ -135,12 +135,13 @@ export interface Cart {
* Price of the cart before duties, shipping and taxes.*/ * Price of the cart before duties, shipping and taxes.*/
subtotalPrice: number subtotalPrice: number
/** /**
* The sum of all the prices of all the items in the cart.*/ * The sum of all the prices of all the items in the cart.
/** * Duties, taxes and discounts included.
* Duties, taxes and discounts included.*/ */
totalPrice: number totalPrice: number
/** /**
* Discounts that have been applied on the cart.*/ * Discounts that have been applied on the cart.
*/
discounts?: Discount[] discounts?: Discount[]
} }
@ -149,17 +150,22 @@ export interface Cart {
*/ */
export interface CartItemBody { export interface CartItemBody {
/** /**
* The product variant's id. * The unique identifier for the product variant.
*/ */
variantId: string variantId: string
/** /**
* The product's id. * The unique identifier for the product, if the variant is not provided.
*/ */
productId?: string productId?: string
/** /**
* The quantity of the product variant. * The quantity of the product variant.
*/ */
quantity?: number quantity?: number
/**
* The product variant's selected options.
*/
optionsSelected?: SelectedOption[]
} }
/** /**

View File

@ -5,6 +5,8 @@ import type { Card, CardFields } from './customer/card'
// Index // Index
export type Checkout = any export type Checkout = any
export type CheckoutBody = any
export type CheckoutTypes = { export type CheckoutTypes = {
card?: Card | CardFields card?: Card | CardFields
address?: Address | AddressFields address?: Address | AddressFields
@ -13,45 +15,43 @@ export type CheckoutTypes = {
hasShipping?: boolean hasShipping?: boolean
} }
export type SubmitCheckoutHook<T extends CheckoutTypes = CheckoutTypes> = { export type SubmitCheckoutHook = {
data: T data: Checkout
input?: T input?: CheckoutBody
fetcherInput: T fetcherInput: CheckoutBody
body: { item: T } body: { item: CheckoutBody }
actionInput: T actionInput: CheckoutBody
} }
export type GetCheckoutHook<T extends CheckoutTypes = CheckoutTypes> = { export type GetCheckoutHook = {
data: T['checkout'] | null data: Checkout | null | undefined
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
mutations: { submit: UseSubmitCheckout } mutations: { submit: UseSubmitCheckout }
} }
export type CheckoutHooks<T extends CheckoutTypes = CheckoutTypes> = { export type CheckoutHooks = {
submitCheckout?: SubmitCheckoutHook<T> submitCheckout?: SubmitCheckoutHook
getCheckout: GetCheckoutHook<T> getCheckout: GetCheckoutHook
} }
export type GetCheckoutHandler<T extends CheckoutTypes = CheckoutTypes> = export type GetCheckoutHandler = GetCheckoutHook & {
GetCheckoutHook<T> & { body: { cartId: string }
body: { cartId: string }
}
export type SubmitCheckoutHandler<T extends CheckoutTypes = CheckoutTypes> =
SubmitCheckoutHook<T> & {
body: { cartId: string }
}
export type CheckoutHandlers<T extends CheckoutTypes = CheckoutTypes> = {
getCheckout: GetCheckoutHandler<T>
submitCheckout?: SubmitCheckoutHandler<T>
} }
export type CheckoutSchema<T extends CheckoutTypes = CheckoutTypes> = { export type SubmitCheckoutHandler = SubmitCheckoutHook & {
body: { cartId: string }
}
export type CheckoutHandlers = {
getCheckout: GetCheckoutHandler
submitCheckout?: SubmitCheckoutHandler
}
export type CheckoutSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: CheckoutHandlers<T> handlers: CheckoutHandlers
} }
} }

View File

@ -11,7 +11,7 @@ export interface Measurement {
*/ */
value: number value: number
/** /**
* The measurement's unit. * The measurement's unit, such as "KILOGRAMS", "GRAMS", "POUNDS" & "OOUNCES".
*/ */
unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES' unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES'
} }

View File

@ -1,28 +1,62 @@
export interface Address { export interface Address {
/**
* The unique identifier for the address.
*/
id: string id: string
/**
* The customer's first name.
*/
mask: string mask: string
} }
export interface AddressFields { export interface AddressFields {
/**
* The type of address.
* @example "billing, shipping"
*/
type: string type: string
/**
* The customer's first name.
*/
firstName: string firstName: string
/**
* The customer's last name.
*/
lastName: string lastName: string
/**
* Company name.
*/
company: string company: string
/**
* The customer's billing address street number.
*/
streetNumber: string streetNumber: string
/**
* The customer's billing address apartment number.
*/
apartments: string apartments: string
/**
* The customer's billing address zip code.
*/
zipCode: string zipCode: string
/**
* The customer's billing address city.
*/
city: string city: string
/**
* The customer's billing address country.
*/
country: string country: string
} }
export type GetAddressesHook = { export interface GetAddressesHook {
data: Address[] | null data: Address[] | null
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
} }
export type AddItemHook = { export interface AddItemHook {
data: Address data: Address
input?: AddressFields input?: AddressFields
fetcherInput: AddressFields fetcherInput: AddressFields
@ -30,7 +64,7 @@ export type AddItemHook = {
actionInput: AddressFields actionInput: AddressFields
} }
export type UpdateItemHook = { export interface UpdateItemHook {
data: Address | null data: Address | null
input: { item?: AddressFields; wait?: number } input: { item?: AddressFields; wait?: number }
fetcherInput: { itemId: string; item: AddressFields } fetcherInput: { itemId: string; item: AddressFields }
@ -38,25 +72,21 @@ export type UpdateItemHook = {
actionInput: AddressFields & { id: string } actionInput: AddressFields & { id: string }
} }
export type RemoveItemHook = { export interface RemoveItemHook {
data: Address | null data: Address | null | undefined
input: { item?: Address } 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 interface CustomerAddressHooks {
getAddresses: GetAddressesHook getAddresses: GetAddressesHook
addItem: AddItemHook addItem: AddItemHook
updateItem: UpdateItemHook updateItem: UpdateItemHook
removeItem: RemoveItemHook removeItem: RemoveItemHook
} }
export type AddressHandler = GetAddressesHook & {
body: { cartId?: string }
}
export type AddItemHandler = AddItemHook & { export type AddItemHandler = AddItemHook & {
body: { cartId: string } body: { cartId: string }
} }

View File

@ -1,102 +1,156 @@
export interface Card { export interface Card {
/**
* Unique identifier for the card.
*/
id: string id: string
/**
* Masked card number.
* @example "************4242"
*/
mask: string mask: string
/**
* The card's brand.
* @example "Visa, Mastercard, etc."
*/
provider: string provider: string
} }
/**
* The fields required to create a new card.
*/
export interface CardFields { export interface CardFields {
/**
* Name on the card.
*/
cardHolder: string cardHolder: string
/**
* The card's number, consisting of 16 digits.
*/
cardNumber: string cardNumber: string
/**
* The card's expiry month and year, in the format MM/YY.
* @example "01/25"
*/
cardExpireDate: string cardExpireDate: string
/**
* The card's security code, consisting of 3 digits.
*/
cardCvc: string cardCvc: string
/**
* The customer's first name.
*/
firstName: string firstName: string
/**
* The customer's last name.
*/
lastName: string lastName: string
/**
* Company name.
*/
company: string company: string
/**
* The customer's billing address street number.
*/
streetNumber: string streetNumber: string
/**
* The customer's billing address zip code.
*/
zipCode: string zipCode: string
/**
* The customer's billing address city.
*/
city: string city: string
/**
* The customer's billing address country.
*/
country: string country: string
} }
export type CustomerCardTypes = { /**
card?: Card * Hook for getting a customer's cards.
fields: CardFields */
} export interface GetCardsHook {
data: Card[] | null
export type GetCardsHook<T extends CustomerCardTypes = CustomerCardTypes> = {
data: T['card'][] | null
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
} }
export type AddItemHook<T extends CustomerCardTypes = CustomerCardTypes> = { /**
data: T['card'] * Hook for adding a card to a customer's account.
input?: T['fields'] */
fetcherInput: T['fields'] export interface AddItemHook {
body: { item: T['fields'] } data: Card
actionInput: T['fields'] input?: CardFields
fetcherInput: CardFields
body: { item: CardFields }
actionInput: CardFields
} }
export type UpdateItemHook<T extends CustomerCardTypes = CustomerCardTypes> = { /**
data: T['card'] | null * Hook for updating a card from a customer's account.
input: { item?: T['fields']; wait?: number } */
fetcherInput: { itemId: string; item: T['fields'] } export interface UpdateItemHook {
body: { itemId: string; item: T['fields'] } data: Card | null | undefined
actionInput: T['fields'] & { id: string } input: { item?: CardFields; wait?: number }
fetcherInput: { itemId: string; item: CardFields }
body: { itemId: string; item: CardFields }
actionInput: CardFields & { id: string }
} }
export type RemoveItemHook<T extends CustomerCardTypes = CustomerCardTypes> = { /**
data: T['card'] | null * Hook for removing a card from a customer's account.
input: { item?: T['card'] } */
export interface RemoveItemHook {
data: Card | null | undefined
input: { item?: Card }
fetcherInput: { itemId: string } fetcherInput: { itemId: string }
body: { itemId: string } body: { itemId: string }
actionInput: { id: string } actionInput: { id: string }
} }
export type CustomerCardHooks<T extends CustomerCardTypes = CustomerCardTypes> = /**
{ * Hooks for add, update & remove items from the cart.
getCards: GetCardsHook<T> */
addItem: AddItemHook<T> export interface CustomerCardHooks {
updateItem: UpdateItemHook<T> getCards: GetCardsHook
removeItem: RemoveItemHook<T> addItem: AddItemHook
} updateItem: UpdateItemHook
removeItem: RemoveItemHook
export type CardsHandler<T extends CustomerCardTypes = CustomerCardTypes> =
GetCardsHook<T> & {
body: { cartId?: string }
}
export type AddItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
AddItemHook<T> & {
body: { cartId: string }
}
export type UpdateItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
UpdateItemHook<T> & {
data: T['card']
body: { cartId: string }
}
export type RemoveItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
RemoveItemHook<T> & {
body: { cartId: string }
}
export type CustomerCardHandlers<
T extends CustomerCardTypes = CustomerCardTypes
> = {
getCards: GetCardsHook<T>
addItem: AddItemHandler<T>
updateItem: UpdateItemHandler<T>
removeItem: RemoveItemHandler<T>
} }
export type CustomerCardSchema< /**
T extends CustomerCardTypes = CustomerCardTypes * Customer card API handler
> = { */
export type AddItemHandler = AddItemHook & {
body: { cartId: string }
}
export type UpdateItemHandler = UpdateItemHook & {
data: Card
body: { cartId: string }
}
export type RemoveItemHandler = RemoveItemHook & {
body: { cartId: string }
}
/**
* Customer card API handlers.
*/
export type CustomerCardHandlers = {
getCards: GetCardsHook
addItem: AddItemHandler
updateItem: UpdateItemHandler
removeItem: RemoveItemHandler
}
/**
* Customer card API endpoints.
*/
export type CustomerCardSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: CustomerCardHandlers<T> handlers: CustomerCardHandlers
} }
} }

View File

@ -1,24 +1,26 @@
export type LoginBody = { export interface LoginBody {
/**
* The user's email address.
*/
email: string email: string
/**
* The user's password.
*/
password: string password: string
} }
export type LoginTypes = { export interface LoginHook {
body: LoginBody
}
export type LoginHook<T extends LoginTypes = LoginTypes> = {
data: null data: null
actionInput: LoginBody actionInput: LoginBody
fetcherInput: LoginBody fetcherInput: LoginBody
body: T['body'] body: LoginBody
} }
export type LoginSchema<T extends LoginTypes = LoginTypes> = { export type LoginSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: { handlers: {
login: LoginHook<T> login: LoginHook
} }
} }
} }

View File

@ -1,17 +1,15 @@
export type LogoutTypes = { export interface LogoutHook {
body: { redirectTo?: string }
}
export type LogoutHook<T extends LogoutTypes = LogoutTypes> = {
data: null data: null
body: T['body'] body: {
redirectTo?: string
}
} }
export type LogoutSchema<T extends LogoutTypes = LogoutTypes> = { export type LogoutSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: { handlers: {
logout: LogoutHook<T> logout: LogoutHook
} }
} }
} }

View File

@ -1,28 +1,43 @@
// TODO: define this type
export type Page = { export type Page = {
// ID of the Web page. /**
* The unique identifier for the page.
*/
id: string id: string
// Page name, as displayed on the storefront. /**
* Page name, as displayed on the storefront.
*/
name: string name: string
// Relative URL on the storefront for this page. /**
* Relative URL on the storefront for this page.
*/
url?: string url?: string
// HTML or variable that populates this pages `<body>` element, in default/desktop view. Required in POST if page type is `raw`. /**
* HTML or variable that populates this pages `<body>` element, in default/desktop view. Required in POST if page type is `raw`.
*/
body: string body: string
// If true, this page appears in the storefronts navigation menu. /**
* If true, this page appears in the storefronts navigation menu.
*/
is_visible?: boolean is_visible?: boolean
// Order in which this page should display on the storefront. (Lower integers specify earlier display.) /**
* Order in which this page should display on the storefront. (Lower integers specify earlier display.)
*/
sort_order?: number sort_order?: number
} }
export type PageTypes = { /**
page: Page * Operation to get all pages.
*/
export type GetAllPagesOperation = {
data: { pages: Page[] }
} }
export type GetAllPagesOperation<T extends PageTypes = PageTypes> = { export type GetPageOperation = {
data: { pages: T['page'][] } data: { page?: Page }
} variables: {
/**
export type GetPageOperation<T extends PageTypes = PageTypes> = { * The unique identifier of the page.
data: { page?: T['page'] } */
variables: { id: string } id: string
}
} }

View File

@ -6,11 +6,11 @@ export interface ProductPrice {
*/ */
value: number value: number
/** /**
* Currency code of the product price. * The currency code for the price. This is a 3-letter ISO 4217 code.
*/ */
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
} }
@ -18,11 +18,11 @@ export interface ProductPrice {
export interface ProductOption { export interface ProductOption {
__typename?: 'MultipleChoiceOption' __typename?: 'MultipleChoiceOption'
/** /**
* The option's id. * The unique identifier for the option.
*/ */
id: string id: string
/** /**
* The option's name. * The product options name.
*/ */
displayName: string displayName: string
/** /**
@ -44,17 +44,17 @@ export interface ProductOptionValues {
export interface ProductVariant { export interface ProductVariant {
/** /**
* The variant's id. * The unique identifier for the variant.
*/ */
id: string | number id: string
/** /**
* The SKU (stock keeping unit) associated with the product variant. * The SKU (stock keeping unit) associated with the product variant.
*/ */
sku: string sku?: string
/** /**
* The product variants name, or the product's name. * The product variants name, or the product's name.
*/ */
name: string name?: string
/** /**
* List of product options. * List of product options.
*/ */
@ -62,11 +62,11 @@ export interface ProductVariant {
/** /**
* The product variants price after all discounts are applied. * The product variants price after all discounts are applied.
*/ */
price: ProductPrice price?: ProductPrice
/** /**
* Product variants price, as quoted by the manufacturer/distributor. * The retail price of the product. This can be used to mark a product as on sale, when `retailPrice` is higher than the `price`.
*/ */
listPrice: ProductPrice retailPrice?: ProductPrice
/** /**
* Indicates if the variant is available for sale. * Indicates if the variant is available for sale.
*/ */
@ -83,11 +83,11 @@ export interface ProductVariant {
export interface Product { export interface Product {
/** /**
* The product's id. * The unique identifier for the product.
*/ */
id: string id: string
/** /**
* The product's name. * The name of the product.
*/ */
name: string name: string
/** /**
@ -107,7 +107,7 @@ export interface Product {
*/ */
slug?: string slug?: string
/** /**
* A human-friendly string for the product, containing U. * Relative URL on the storefront for the product.
*/ */
path?: string path?: string
/** /**
@ -123,7 +123,7 @@ export interface Product {
*/ */
price: ProductPrice price: ProductPrice
/** /**
* The product's price. * List of product's options.
*/ */
options: ProductOption[] options: ProductOption[]
/** /**
@ -140,13 +140,14 @@ export interface SearchProductsBody {
/** /**
* The category ID to filter the products by. * The category ID to filter the products by.
*/ */
categoryId?: string | number categoryId?: string
/** /**
* The brand ID to filter the products by. * The brand ID to filter the products by.
*/ */
brandId?: string | number brandId?: string
/** /**
* The sort order to sort the products by. * The sort key to sort the products by.
* @example 'trending-desc' | 'latest-desc' | 'price-asc' | 'price-desc'
*/ */
sort?: string sort?: string
/** /**

View File

@ -1,26 +1,34 @@
export type SignupBody = { export type SignupBody = {
/**
* The user's first name.
*/
firstName: string firstName: string
/**
* The user's last name.
*/
lastName: string lastName: string
/**
* The user's email address.
*/
email: string email: string
/**
* The user's password.
*/
password: string password: string
} }
export type SignupTypes = { export interface SignupHook {
body: SignupBody
}
export type SignupHook<T extends SignupTypes = SignupTypes> = {
data: null data: null
body: T['body'] body: SignupBody
actionInput: T['body'] actionInput: SignupBody
fetcherInput: T['body'] fetcherInput: SignupBody
} }
export type SignupSchema<T extends SignupTypes = SignupTypes> = { export type SignupSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: { handlers: {
signup: SignupHook<T> signup: SignupHook
} }
} }
} }

View File

@ -1,20 +1,51 @@
export type Category = { export type Category = {
/**
* Unique identifier for the category.
*/
id: string id: string
/**
* Name of the category.
*/
name: string name: string
/**
* A human-friendly unique string for the category, automatically generated from its name.
* @example "t-shirts"
*/
slug: string slug: string
/**
* Relative URL on the storefront for the category.
* @example /t-shirts
*/
path: string path: string
} }
export type Brand = any export type Brand = {
/**
export type SiteTypes = { * Unique identifier for the brand.
category: Category */
brand: Brand id: string
/**
* Name of the brand.
*/
name: string
/**
* A human-friendly unique string for the category, automatically generated from its name.
* @example "acme"
*/
slug: string
/**
* Relative URL on the storefront for this brand.
* @example "/acme"
*/
path: string
} }
export type GetSiteInfoOperation<T extends SiteTypes = SiteTypes> = { /**
* Operation to get site information. This includes categories and brands.
*/
export type GetSiteInfoOperation = {
data: { data: {
categories: T['category'][] categories: Category[]
brands: T['brand'][] brands: Brand[]
} }
} }

View File

@ -1,60 +1,98 @@
// TODO: define this type import { Product } from './product'
export type Wishlist = any
export type WishlistItemBody = { export interface WishlistItem {
variantId: string | number /**
* The unique identifier for the item.
*/
id: string
/**
* The unique identifier for the product associated with the wishlist item.
*/
productId: string productId: string
/**
* The unique identifier for the product variant associated with the wishlist item.
*/
variantId: string
/**
* The product associated with the wishlist item.
*/
product: Product
} }
export type WishlistTypes = { export interface Wishlist {
wishlist: Wishlist /**
itemBody: WishlistItemBody * The unique identifier for the wishlist.
*/
id: string
/**
* List of items in the wishlist.
*/
items: WishlistItem[]
/**
* TODO: Spree provider specific
*/
token?: string
} }
export type GetWishlistHook<T extends WishlistTypes = WishlistTypes> = { export interface WishlistItemBody {
data: T['wishlist'] | null /**
* The product's variant id.
*/
variantId: string
/**
* The product's ID.
*/
productId: string
/**
* TODO: Spree provider specific
*/
wishlistToken?: string
}
export interface GetWishlistHook {
data: Wishlist | null | undefined
body: { includeProducts?: boolean } body: { includeProducts?: boolean }
input: { includeProducts?: boolean } input: { includeProducts?: boolean }
fetcherInput: { customerId: string; includeProducts?: boolean } fetcherInput: { customerId: string; includeProducts?: boolean }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
} }
export type AddItemHook<T extends WishlistTypes = WishlistTypes> = { export interface AddItemHook {
data: T['wishlist'] data: Wishlist | null | undefined
body: { item: T['itemBody'] } body: { item: WishlistItemBody }
fetcherInput: { item: T['itemBody'] } fetcherInput: { item: WishlistItemBody }
actionInput: T['itemBody'] actionInput: WishlistItemBody
} }
export type RemoveItemHook<T extends WishlistTypes = WishlistTypes> = { export interface RemoveItemHook {
data: T['wishlist'] | null data: Wishlist | null | undefined
body: { itemId: string } body: { itemId: string; wishlistToken?: string }
fetcherInput: { itemId: string } fetcherInput: { itemId: string; wishlistToken?: string }
actionInput: { id: string } actionInput: { id: string }
input: { wishlist?: { includeProducts?: boolean } } input: { wishlist?: { includeProducts?: boolean } }
} }
export type WishlistSchema<T extends WishlistTypes = WishlistTypes> = { export type WishlistSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: { handlers: {
getWishlist: GetWishlistHook<T> & { getWishlist: GetWishlistHook & {
data: T['wishlist'] | null data: Wishlist | null
body: { customerToken?: string } body: { customerToken?: string }
} }
addItem: AddItemHook<T> & { addItem: AddItemHook & {
body: { customerToken?: string } body: { customerToken?: string }
} }
removeItem: RemoveItemHook<T> & { removeItem: RemoveItemHook & {
body: { customerToken?: string } body: { customerToken?: string }
} }
} }
} }
} }
export type GetCustomerWishlistOperation< export interface GetCustomerWishlistOperation {
T extends WishlistTypes = WishlistTypes data: { wishlist?: Wishlist }
> = {
data: { wishlist?: T['wishlist'] }
variables: { customerId: string } variables: { customerId: string }
} }

View File

@ -5,7 +5,7 @@ import type { AddItemHook } from '../types/wishlist'
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 = mutationFetcher export const fetcher = mutationFetcher

View File

@ -5,7 +5,7 @@ import type { RemoveItemHook } from '../types/wishlist'
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 { GetWishlistHook } from '../types/wishlist'
import type { Provider } from '..' import type { Provider } from '..'
export type UseWishlist< export type UseWishlist<
H extends SWRHook<GetWishlistHook<any>> = SWRHook<GetWishlistHook> H extends SWRHook<GetWishlistHook> = SWRHook<GetWishlistHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<GetWishlistHook> = SWRFetcher export const fetcher: HookFetcherFn<GetWishlistHook> = SWRFetcher

View File

@ -1,25 +1,17 @@
import type { AddItemHook } from '@vercel/commerce/types/customer/address' import useAddItem, {
import type { MutationHook } from '@vercel/commerce/utils/types' UseAddItem,
import { useCallback } from 'react' } from '@vercel/commerce/customer/address/use-add-item'
import useAddItem, { UseAddItem } from '@vercel/commerce/customer/address/use-add-item' import { MutationHook } from '@vercel/commerce/utils/types'
import { useCheckoutContext } from '@components/checkout/context'
export default useAddItem as UseAddItem<typeof handler> export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<AddItemHook> = { export const handler: MutationHook<any> = {
fetchOptions: { fetchOptions: {
query: '_', query: '',
method: '_',
}, },
useHook: () => async fetcher({ input, options, fetch }) {},
function useHook() { useHook:
const { setAddressFields } = useCheckoutContext() ({ fetch }) =>
return useCallback( () =>
async function addItem(input) { async () => ({}),
setAddressFields(input)
return undefined
},
[setAddressFields]
)
},
} }

View File

@ -1,25 +1,17 @@
import type { AddItemHook } from '@vercel/commerce/types/customer/card' import useAddItem, {
import type { MutationHook } from '@vercel/commerce/utils/types' UseAddItem,
import { useCallback } from 'react' } from '@vercel/commerce/customer/card/use-add-item'
import useAddItem, { UseAddItem } from '@vercel/commerce/customer/card/use-add-item' import { MutationHook } from '@vercel/commerce/utils/types'
import { useCheckoutContext } from '@components/checkout/context'
export default useAddItem as UseAddItem<typeof handler> export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<AddItemHook> = { export const handler: MutationHook<any> = {
fetchOptions: { fetchOptions: {
url: '_', query: '',
method: '_',
}, },
useHook: () => async fetcher({ input, options, fetch }) {},
function useHook() { useHook:
const { setCardFields } = useCheckoutContext() ({ fetch }) =>
return useCallback( () =>
async function addItem(input) { async () => ({}),
setCardFields(input)
return undefined
},
[setCardFields]
)
},
} }

View File

@ -1,9 +1 @@
import { LoginBody, LoginTypes } from '@vercel/commerce/types/login'
export * from '@vercel/commerce/types/login' export * from '@vercel/commerce/types/login'
export type LoginHook<T extends LoginTypes = LoginTypes> = {
data: null
actionInput: LoginBody
fetcherInput: LoginBody
body: T['body']
}

View File

@ -26,6 +26,7 @@ function normalizeVariants(
if (!Array.isArray(variants)) return [] if (!Array.isArray(variants)) return []
return variants?.map((variant) => ({ return variants?.map((variant) => ({
id: variant.id, id: variant.id,
sku: variant.sku ?? variant.id,
options: Object.entries(variant.options).map( options: Object.entries(variant.options).map(
([variantGroupId, variantOptionId]) => { ([variantGroupId, variantOptionId]) => {
const variantGroupFromId = variantGroups.find( const variantGroupFromId = variantGroups.find(

View File

@ -1,16 +1,6 @@
import type { Wishlist } from '@vercel/commerce/types/wishlist'
import { HookFetcher } from '@vercel/commerce/utils/types' import { HookFetcher } from '@vercel/commerce/utils/types'
export type Wishlist = {
items: [
{
product_id: number
variant_id: number
id: number
product: any
}
]
}
export interface UseWishlistOptions { export interface UseWishlistOptions {
includeProducts?: boolean includeProducts?: boolean
} }

View File

@ -1,35 +1,29 @@
import type { OperationContext } from '@vercel/commerce/api/operations' import type { OperationContext } from '@vercel/commerce/api/operations'
import type { KiboCommerceConfig } from '../index' import type { KiboCommerceConfig } from '../index'
import { getAllPagesQuery } from '../queries/get-all-pages-query' import { getAllPagesQuery } from '../queries/get-all-pages-query'
import { GetPagesQueryParams } from "../../types/page";
import { normalizePage } from '../../lib/normalize' import { normalizePage } from '../../lib/normalize'
export type GetAllPagesResult< export type GetAllPagesResult<T extends { pages: any[] } = { pages: any[] }> = T
T extends { pages: any[] } = { pages: any[] }
> = T
export default function getAllPagesOperation({ export default function getAllPagesOperation({
commerce, commerce,
}: OperationContext<any>) { }: OperationContext<any>) {
async function getAllPages({ async function getAllPages({
query = getAllPagesQuery, query = getAllPagesQuery,
config, config,
variables,
}: { }: {
url?: string url?: string
config?: Partial<KiboCommerceConfig> config?: Partial<KiboCommerceConfig>
variables?: GetPagesQueryParams
preview?: boolean preview?: boolean
query?: string query?: string
} = {}): Promise<GetAllPagesResult> { } = {}): Promise<GetAllPagesResult> {
const cfg = commerce.getConfig(config) const cfg = commerce.getConfig(config)
variables = { const variables = {
documentListName: cfg.documentListName documentListName: cfg.documentListName,
} }
const { data } = await cfg.fetch(query, { variables }); const { data } = await cfg.fetch(query, { variables })
const pages = data.documentListDocuments.items.map(normalizePage); const pages = data.documentListDocuments.items.map(normalizePage)
return { pages } return { pages }
} }

View File

@ -2,14 +2,11 @@ import type {
OperationContext, OperationContext,
OperationOptions, OperationOptions,
} from '@vercel/commerce/api/operations' } from '@vercel/commerce/api/operations'
import type { import type { GetCustomerWishlistOperation } from '@vercel/commerce/types/wishlist'
GetCustomerWishlistOperation,
Wishlist,
} from '@vercel/commerce/types/wishlist'
// import type { RecursivePartial, RecursiveRequired } from '../utils/types' // import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { KiboCommerceConfig } from '..' import { KiboCommerceConfig } from '..'
// import getAllProducts, { ProductEdge } from './get-all-products' // import getAllProducts, { ProductEdge } from './get-all-products'
import {getCustomerWishlistQuery} from '../queries/get-customer-wishlist-query' import { getCustomerWishlistQuery } from '../queries/get-customer-wishlist-query'
export default function getCustomerWishlistOperation({ export default function getCustomerWishlistOperation({
commerce, commerce,
@ -40,14 +37,15 @@ export default function getCustomerWishlistOperation({
config?: KiboCommerceConfig config?: KiboCommerceConfig
includeProducts?: boolean includeProducts?: boolean
}): Promise<T['data']> { }): Promise<T['data']> {
let customerWishlist ={} let customerWishlist = {}
try { try {
config = commerce.getConfig(config) config = commerce.getConfig(config)
const result= await config?.fetch(getCustomerWishlistQuery,{variables}) const result = await config?.fetch(getCustomerWishlistQuery, {
customerWishlist= result?.data?.customerWishlist; variables,
} catch(e) { })
customerWishlist= {} customerWishlist = result?.data?.customerWishlist
} catch (e) {
customerWishlist = {}
} }
return { wishlist: customerWishlist as any } return { wishlist: customerWishlist as any }

View File

@ -1,15 +1,11 @@
import type { import type { OperationContext } from '@vercel/commerce/api/operations'
OperationContext,
} from '@vercel/commerce/api/operations'
import type { KiboCommerceConfig, KiboCommerceProvider } from '..' import type { KiboCommerceConfig, KiboCommerceProvider } from '..'
import { normalizePage } from '../../lib/normalize' import { normalizePage } from '../../lib/normalize'
import { getPageQuery } from '../queries/get-page-query' import { getPageQuery } from '../queries/get-page-query'
import type { Page, GetPageQueryParams } from "../../types/page"; import type { Page, GetPageOperation } from '../../types/page'
import type { Document } from '../../../schema' import type { Document } from '../../../schema'
export default function getPageOperation({ export default function getPageOperation({ commerce }: OperationContext<any>) {
commerce,
}: OperationContext<any>) {
async function getPage<T extends Page>({ async function getPage<T extends Page>({
url, url,
variables, variables,
@ -17,18 +13,21 @@ export default function getPageOperation({
preview, preview,
}: { }: {
url?: string url?: string
variables: GetPageQueryParams variables: GetPageOperation['variables']
config?: Partial<KiboCommerceConfig> config?: Partial<KiboCommerceConfig>
preview?: boolean preview?: boolean
}): Promise<any> { }): Promise<any> {
// RecursivePartial forces the method to check for every prop in the data, which is // RecursivePartial forces the method to check for every prop in the data, which is
// required in case there's a custom `url` // required in case there's a custom `url`
const cfg = commerce.getConfig(config) const cfg = commerce.getConfig(config)
const pageVariables = { documentListName: cfg.documentListName, filter: `id eq ${variables.id}` } const pageVariables = {
documentListName: cfg.documentListName,
filter: `id eq ${variables.id}`,
}
const { data } = await cfg.fetch(getPageQuery, { variables: pageVariables }) const { data } = await cfg.fetch(getPageQuery, { variables: pageVariables })
const firstPage = data.documentListDocuments.items?.[0]; const firstPage = data.documentListDocuments.items?.[0]
const page = firstPage as Document const page = firstPage as Document
if (preview || page?.properties?.is_visible) { if (preview || page?.properties?.is_visible) {
return { page: normalizePage(page as any) } return { page: normalizePage(page as any) }

View File

@ -1,6 +1,7 @@
import type { PrCategory, CustomerAccountInput, Document } from '../../schema' import type { PrCategory, CustomerAccountInput, Document } from '../../schema'
import { Page } from '../types/page'; import { Page } from '../types/page'
import { Customer } from '../types/customer' import { Customer } from '../types/customer'
import { WishlistItem } from '@vercel/commerce/types/wishlist'
export function normalizeProduct(productNode: any, config: any): any { export function normalizeProduct(productNode: any, config: any): any {
const product = { const product = {
@ -57,7 +58,7 @@ export function normalizePage(page: Document): Page {
url: page.properties.url, url: page.properties.url,
body: page.properties.body, body: page.properties.body,
is_visible: page.properties.is_visible, is_visible: page.properties.is_visible,
sort_order: page.properties.sort_order sort_order: page.properties.sort_order,
} }
} }
@ -91,7 +92,7 @@ export function normalizeCustomer(customer: CustomerAccountInput): Customer {
lastName: customer.lastName, lastName: customer.lastName,
email: customer.emailAddress, email: customer.emailAddress,
userName: customer.userName, userName: customer.userName,
isAnonymous: customer.isAnonymous isAnonymous: customer.isAnonymous,
} }
} }
@ -133,11 +134,13 @@ export function normalizeCategory(category: PrCategory): any {
export function normalizeWishlistItem( export function normalizeWishlistItem(
item: any, item: any,
config: any, config: any,
includeProducts=false includeProducts = false
): any { ): WishlistItem {
if (includeProducts) { if (includeProducts) {
return { return {
id: item.id, id: item.id,
productId: String(item.product.productCode),
variantId: item.product.variationProductCode,
product: getProuducts(item, config), product: getProuducts(item, config),
} }
} else { } else {

View File

@ -1,35 +1,16 @@
import * as Core from '@vercel/commerce/types/page'
export type Maybe<T> = T | null
export type Scalars = {
ID: string
String: string
Boolean: boolean
Int: number
Float: number
/** The `AnyScalar` type allows any scalar value by examining the input and passing the serialize, parseValue, and parseLiteral operations to their respective types. */
AnyScalar: any
/** DateTime custom scalar type */
DateTime: any
/** Object custom scalar type */
Object: any
}
export * from '@vercel/commerce/types/page' export * from '@vercel/commerce/types/page'
export type Page = Core.Page export type Maybe<T> = T | null
export type Scalars = {
export type PageTypes = { ID: string
page: Page String: string
Boolean: boolean
Int: number
Float: number
/** The `AnyScalar` type allows any scalar value by examining the input and passing the serialize, parseValue, and parseLiteral operations to their respective types. */
AnyScalar: any
/** DateTime custom scalar type */
DateTime: any
/** Object custom scalar type */
Object: any
} }
export type GetPagesQueryParams = {
documentListName: Maybe<Scalars["String"]>
}
export type GetPageQueryParams = {
id: Maybe<Scalars["String"]>
documentListName: Maybe<Scalars["String"]>
}
export type GetAllPagesOperation = Core.GetAllPagesOperation<PageTypes>
export type GetPageOperation = Core.GetPageOperation<PageTypes>

View File

@ -4,9 +4,7 @@ import { GetProductOperation } from '@vercel/commerce/types/product'
import data from '../../data.json' import data from '../../data.json'
import type { OperationContext } from '@vercel/commerce/api/operations' import type { OperationContext } from '@vercel/commerce/api/operations'
export default function getProductOperation({ export default function getProductOperation(_p: OperationContext<any>) {
commerce,
}: OperationContext<any>) {
async function getProduct<T extends GetProductOperation>({ async function getProduct<T extends GetProductOperation>({
query = '', query = '',
variables, variables,

View File

@ -1,5 +1,5 @@
import { OperationContext } from '@vercel/commerce/api/operations' import { OperationContext } from '@vercel/commerce/api/operations'
import { Category } from '@vercel/commerce/types/site' import { Category, GetSiteInfoOperation } from '@vercel/commerce/types/site'
import { LocalConfig } from '../index' import { LocalConfig } from '../index'
export type GetSiteInfoResult< export type GetSiteInfoResult<

View File

@ -7,7 +7,8 @@
"path": "/new-short-sleeve-t-shirt", "path": "/new-short-sleeve-t-shirt",
"slug": "new-short-sleeve-t-shirt", "slug": "new-short-sleeve-t-shirt",
"price": { "value": 25, "currencyCode": "USD" }, "price": { "value": 25, "currencyCode": "USD" },
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>", "description": "Show off your love for Next.js and Vercel with this unique, limited edition t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last - only 200 of these shirts will be made! All proceeds will be donated to charity.",
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last - only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [ "images": [
{ {
"url": "/assets/drop-shirt-0.png", "url": "/assets/drop-shirt-0.png",
@ -31,6 +32,8 @@
"variants": [ "variants": [
{ {
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=", "id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"name": "New Short Sleeve T-Shirt",
"sku": "new-short-sleeve-t-shirt",
"options": [ "options": [
{ {
"__typename": "MultipleChoiceOption", "__typename": "MultipleChoiceOption",
@ -80,6 +83,7 @@
"path": "/lightweight-jacket", "path": "/lightweight-jacket",
"slug": "lightweight-jacket", "slug": "lightweight-jacket",
"price": { "value": 249.99, "currencyCode": "USD" }, "price": { "value": 249.99, "currencyCode": "USD" },
"description": "Show off your love for Next.js and Vercel with this unique, limited edition t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last - only 200 of these shirts will be made! All proceeds will be donated to charity.",
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>", "descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [ "images": [
{ {
@ -104,6 +108,8 @@
"variants": [ "variants": [
{ {
"id": "Z2lkOid8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=", "id": "Z2lkOid8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"name": "Lightweight Jacket",
"sku": "lightweight-jacket",
"options": [ "options": [
{ {
"__typename": "MultipleChoiceOption", "__typename": "MultipleChoiceOption",
@ -153,6 +159,7 @@
"path": "/shirt", "path": "/shirt",
"slug": "shirt", "slug": "shirt",
"price": { "value": 25, "currencyCode": "USD" }, "price": { "value": 25, "currencyCode": "USD" },
"description": "Show off your love for Next.js and Vercel with this unique, limited edition t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last - only 200 of these shirts will be made! All proceeds will be donated to charity.",
"descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>", "descriptionHtml": "<p><span>Show off your love for Next.js and Vercel with this unique,&nbsp;</span><strong>limited edition</strong><span>&nbsp;t-shirt. This design is part of a limited run, numbered drop at the June 2021 Next.js Conf. It features a unique, handcrafted triangle design. Get it while supplies last only 200 of these shirts will be made!&nbsp;</span><strong>All proceeds will be donated to charity.</strong></p>",
"images": [ "images": [
{ {
@ -189,6 +196,8 @@
"variants": [ "variants": [
{ {
"id": "Z2lkOi8vc2hvcGlmeS9Qcms9kdWN0LzU0NDczMjUwMjQ0MjAss=", "id": "Z2lkOi8vc2hvcGlmeS9Qcms9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"name": "Shirt",
"sku": "shirt",
"options": [ "options": [
{ {
"__typename": "MultipleChoiceOption", "__typename": "MultipleChoiceOption",

View File

@ -1,19 +1,8 @@
import { HookFetcher } from '@vercel/commerce/utils/types' import { HookFetcher } from '@vercel/commerce/utils/types'
import type { Product } from '@vercel/commerce/types/product' import { Wishlist } from '@vercel/commerce/types/wishlist'
const defaultOpts = {} const defaultOpts = {}
export type Wishlist = {
items: [
{
product_id: number
variant_id: number
id: number
product: Product
}
]
}
export interface UseWishlistOptions { export interface UseWishlistOptions {
includeProducts?: boolean includeProducts?: boolean
} }

View File

@ -1,5 +1,3 @@
import * as Core from '@vercel/commerce/types/cart'
export * from '@vercel/commerce/types/cart' export * from '@vercel/commerce/types/cart'
export interface OrdercloudCart { export interface OrdercloudCart {
@ -97,30 +95,3 @@ export interface OrdercloudLineItem {
Specs: [] Specs: []
xp: null xp: null
} }
/**
* 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

@ -1,4 +1 @@
import * as Core from '@vercel/commerce/types/checkout' export * from '@vercel/commerce/types/checkout'
export type CheckoutTypes = Core.CheckoutTypes
export type CheckoutSchema = Core.CheckoutSchema<CheckoutTypes>

View File

@ -1,8 +1,4 @@
import * as Core from '@vercel/commerce/types/customer/address' export * from '@vercel/commerce/types/customer/address'
export type CustomerAddressTypes = Core.CustomerAddressTypes
export type CustomerAddressSchema =
Core.CustomerAddressSchema<CustomerAddressTypes>
export interface OrdercloudAddress { export interface OrdercloudAddress {
ID: string ID: string

View File

@ -1,7 +1,4 @@
import * as Core from '@vercel/commerce/types/customer/card' export * from '@vercel/commerce/types/customer/card'
export type CustomerCardTypes = Core.CustomerCardTypes
export type CustomerCardSchema = Core.CustomerCardSchema<CustomerCardTypes>
export interface OredercloudCreditCard { export interface OredercloudCreditCard {
ID: string ID: string

View File

@ -8,6 +8,7 @@ export function normalize(product: RawProduct): Product {
name: product.Name, name: product.Name,
description: product.Description, description: product.Description,
slug: product.ID, slug: product.ID,
path: `/${product.ID}`,
images: product.xp.Images, images: product.xp.Images,
price: { price: {
value: product.xp.Price, value: product.xp.Price,
@ -16,6 +17,8 @@ export function normalize(product: RawProduct): Product {
variants: product.xp.Variants?.length variants: product.xp.Variants?.length
? product.xp.Variants.map((variant) => ({ ? product.xp.Variants.map((variant) => ({
id: variant.ID, id: variant.ID,
sku: variant.ID,
name: product.Name,
options: variant.Specs.map((spec) => ({ options: variant.Specs.map((spec) => ({
id: spec.SpecID, id: spec.SpecID,
__typename: 'MultipleChoiceOption', __typename: 'MultipleChoiceOption',
@ -27,12 +30,7 @@ export function normalize(product: RawProduct): Product {
], ],
})), })),
})) }))
: [ : [],
{
id: '',
options: [],
},
],
options: product.xp.Specs?.length options: product.xp.Specs?.length
? product.xp.Specs.map((spec) => ({ ? product.xp.Specs.map((spec) => ({
id: spec.ID, id: spec.ID,

View File

@ -1,19 +1,8 @@
import { HookFetcher } from '@vercel/commerce/utils/types' import { HookFetcher } from '@vercel/commerce/utils/types'
import type { Product } from '@vercel/commerce/types/product' import type { Wishlist } from '@vercel/commerce/types/wishlist'
const defaultOpts = {} const defaultOpts = {}
export type Wishlist = {
items: [
{
product_id: number
variant_id: number
id: number
product: Product
}
]
}
export interface UseWishlistOptions { export interface UseWishlistOptions {
includeProducts?: boolean includeProducts?: boolean
} }

View File

@ -6,9 +6,12 @@ import * as Query from '../../utils/queries'
export type Page = any export type Page = any
export type GetAllPagesResult<T extends { pages: any[] } = { pages: Page[] }> = T export type GetAllPagesResult<T extends { pages: any[] } = { pages: Page[] }> =
T
export default function getAllPagesOperation({ commerce }: OperationContext<Provider>) { export default function getAllPagesOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllPages({ async function getAllPages({
query = Query.PageMany, query = Query.PageMany,
config, config,
@ -34,11 +37,15 @@ export default function getAllPagesOperation({ commerce }: OperationContext<Prov
} }
) )
const pages = data.pages?.edges?.map(({ node: { title: name, slug, ...node } }: PageCountableEdge) => ({ const pages = data?.pages?.edges?.map(
...node, ({ node: { title: name, slug, ...node } }: PageCountableEdge) =>
url: `/${locale}/${slug}`, ({
name, id: node.id,
})) url: `/${locale}/${slug}`,
body: node.content || '',
name,
} ?? [])
)
return { pages } return { pages }
} }

View File

@ -3,15 +3,16 @@ import { ProductCountableEdge } from '../../../schema'
import type { Provider, SaleorConfig } from '..' import type { Provider, SaleorConfig } from '..'
import { getAllProductsPathsQuery } from '../../utils/queries' import { getAllProductsPathsQuery } from '../../utils/queries'
import fetchAllProducts from '../utils/fetch-all-products'
export type GetAllProductPathsResult = { export type GetAllProductPathsResult = {
products: Array<{ path: string }> products: Array<{ path: string }>
} }
export default function getAllProductPathsOperation({ commerce }: OperationContext<Provider>) { export default function getAllProductPathsOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllProductPaths({ async function getAllProductPaths({
query, query = getAllProductsPathsQuery,
config, config,
variables, variables,
}: { }: {
@ -21,16 +22,15 @@ export default function getAllProductPathsOperation({ commerce }: OperationConte
} = {}): Promise<GetAllProductPathsResult> { } = {}): Promise<GetAllProductPathsResult> {
config = commerce.getConfig(config) config = commerce.getConfig(config)
const products = await fetchAllProducts({ const { data }: any = await config.fetch(query, { variables })
config,
query: getAllProductsPathsQuery,
variables,
})
return { return {
products: products?.map(({ node: { slug } }: ProductCountableEdge) => ({ products: data?.products?.edges?.map(
path: `/${slug}`, ({ node: { slug } }: ProductCountableEdge) =>
})), ({
path: `/${slug}`,
} ?? [])
),
} }
} }

View File

@ -12,7 +12,9 @@ type ReturnType = {
products: Product[] products: Product[]
} }
export default function getAllProductsOperation({ commerce }: OperationContext<Provider>) { export default function getAllProductsOperation({
commerce,
}: OperationContext<Provider>) {
async function getAllProducts({ async function getAllProducts({
query = Query.ProductMany, query = Query.ProductMany,
variables, variables,
@ -46,13 +48,18 @@ export default function getAllProductsOperation({ commerce }: OperationContext<P
if (featured) { if (featured) {
const products = const products =
data.collection.products?.edges?.map(({ node: p }: ProductCountableEdge) => normalizeProduct(p)) ?? [] data?.collection.products?.edges?.map(
({ node: p }: ProductCountableEdge) => normalizeProduct(p)
) ?? []
return { return {
products, products,
} }
} else { } else {
const products = data.products?.edges?.map(({ node: p }: ProductCountableEdge) => normalizeProduct(p)) ?? [] const products =
data?.products?.edges?.map(({ node: p }: ProductCountableEdge) =>
normalizeProduct(p)
) ?? []
return { return {
products, products,

View File

@ -8,7 +8,9 @@ export type Page = any
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
export default function getPageOperation({ commerce }: OperationContext<Provider>) { export default function getPageOperation({
commerce,
}: OperationContext<Provider>) {
async function getPage({ async function getPage({
query = Query.PageOne, query = Query.PageOne,
variables, variables,
@ -21,9 +23,7 @@ export default function getPageOperation({ commerce }: OperationContext<Provider
}): Promise<GetPageResult> { }): Promise<GetPageResult> {
const { fetch, locale = 'en-US' } = commerce.getConfig(config) const { fetch, locale = 'en-US' } = commerce.getConfig(config)
const { const { data } = await fetch(
data: { page },
} = await fetch(
query, query,
{ variables }, { variables },
{ {
@ -36,13 +36,15 @@ export default function getPageOperation({ commerce }: OperationContext<Provider
) )
return { return {
page: page page:
? { data && data.page
...page, ? {
name: page.title, ...data.page,
url: `/${locale}/${page.slug}`, name: data.page.title,
} body: data.page.content || '',
: null, url: `/${locale}/${data.page.slug}`,
}
: null,
} }
} }

View File

@ -12,7 +12,9 @@ type ReturnType = {
product: any product: any
} }
export default function getProductOperation({ commerce }: OperationContext<Provider>) { export default function getProductOperation({
commerce,
}: OperationContext<Provider>) {
async function getProduct({ async function getProduct({
query = Query.ProductOneBySlug, query = Query.ProductOneBySlug,
variables, variables,
@ -38,7 +40,7 @@ export default function getProductOperation({ commerce }: OperationContext<Provi
) )
return { return {
product: data?.product ? normalizeProduct(data.product) : null, product: data && data.product ? normalizeProduct(data.product) : null,
} }
} }

View File

@ -1,41 +0,0 @@
import { ProductCountableEdge } from '../../../schema'
import { SaleorConfig } from '..'
const fetchAllProducts = async ({
config,
query,
variables,
acc = [],
cursor,
}: {
config: SaleorConfig
query: string
acc?: ProductCountableEdge[]
variables?: any
cursor?: string
}): Promise<ProductCountableEdge[]> => {
const { data } = await config.fetch(query, {
variables: { ...variables, cursor },
})
const edges: ProductCountableEdge[] = data.products?.edges ?? []
const hasNextPage = data.products?.pageInfo?.hasNextPage
acc = acc.concat(edges)
if (hasNextPage) {
const cursor = edges.pop()?.cursor
if (cursor) {
return fetchAllProducts({
config,
query,
variables,
acc,
cursor,
})
}
}
return acc
}
export default fetchAllProducts

View File

@ -38,7 +38,7 @@ export const handler: SWRHook<SearchProductsHook> = {
let edges let edges
if (categoryId) { if (categoryId) {
edges = data.collection?.products?.edges ?? [] edges = data?.collection?.products?.edges ?? []
// FIXME @zaiste, no `vendor` in Saleor // FIXME @zaiste, no `vendor` in Saleor
// if (brandId) { // if (brandId) {
// edges = edges.filter( // edges = edges.filter(
@ -47,11 +47,13 @@ export const handler: SWRHook<SearchProductsHook> = {
// ) // )
// } // }
} else { } else {
edges = data.products?.edges ?? [] edges = data?.products?.edges ?? []
} }
return { return {
products: edges.map(({ node }: ProductCountableEdge) => normalizeProduct(node)), products: edges.map(({ node }: ProductCountableEdge) =>
normalizeProduct(node)
),
found: !!edges.length, found: !!edges.length,
} }
}, },

View File

@ -6,17 +6,19 @@ import * as query from './queries'
const getCategories = async (config: SaleorConfig): Promise<Category[]> => { const getCategories = async (config: SaleorConfig): Promise<Category[]> => {
const { data } = await config.fetch(query.CollectionMany, { const { data } = await config.fetch(query.CollectionMany, {
variables: { variables: {
first: 100, first: 50,
}, },
}) })
return ( return (
data.collections?.edges?.map(({ node: { id, name, slug } }: CollectionCountableEdge) => ({ data?.collections?.edges?.map(
id, ({ node: { id, name, slug } }: CollectionCountableEdge) => ({
name, id,
slug, name,
path: `/${slug}`, slug,
})) ?? [] path: `/${slug}`,
})
) ?? []
) )
} }

View File

@ -30,7 +30,7 @@ const getVendors = async (config: SaleorConfig): Promise<BrandEdge[]> => {
// node: { // node: {
// entityId: id, // entityId: id,
// name: v, // name: v,
// path: `brands/${id}`, // path: `/${id}`,
// }, // },
// } // }
// }) // })

View File

@ -1,6 +1,12 @@
import { Product } from '@vercel/commerce/types/product' import { Product } from '@vercel/commerce/types/product'
import { Product as SaleorProduct, Checkout, CheckoutLine, Money, ProductVariant } from '../../schema' import {
Product as SaleorProduct,
Checkout,
CheckoutLine,
Money,
ProductVariant,
} from '../../schema'
import type { Cart, LineItem } from '../types' import type { Cart, LineItem } from '../types'
@ -19,11 +25,14 @@ const normalizeProductOptions = (options: ProductVariant[]) => {
?.map((option) => option?.attributes) ?.map((option) => option?.attributes)
.flat(1) .flat(1)
.reduce<any>((acc, x) => { .reduce<any>((acc, x) => {
if (acc.find(({ displayName }: any) => displayName === x.attribute.name)) { if (
acc.find(({ displayName }: any) => displayName === x.attribute.name)
) {
return acc.map((opt: any) => { return acc.map((opt: any) => {
return opt.displayName === x.attribute.name return opt.displayName === x.attribute.name
? { ? {
...opt, ...opt,
id: x.attribute.id,
values: [ values: [
...opt.values, ...opt.values,
...x.values.map((value: any) => ({ ...x.values.map((value: any) => ({
@ -37,6 +46,7 @@ const normalizeProductOptions = (options: ProductVariant[]) => {
return acc.concat({ return acc.concat({
__typename: 'MultipleChoiceOption', __typename: 'MultipleChoiceOption',
id: x.attribute.id,
displayName: x.attribute.name, displayName: x.attribute.name,
variant: 'size', variant: 'size',
values: x.values.map((value: any) => ({ values: x.values.map((value: any) => ({
@ -54,7 +64,7 @@ const normalizeProductVariants = (variants: ProductVariant[]) => {
return { return {
id, id,
name, name,
sku: sku ?? id, ...(!!sku && { sku }),
price, price,
listPrice: price, listPrice: price,
requiresShipping: true, requiresShipping: true,
@ -64,23 +74,41 @@ const normalizeProductVariants = (variants: ProductVariant[]) => {
} }
export function normalizeProduct(productNode: SaleorProduct): Product { export function normalizeProduct(productNode: SaleorProduct): Product {
const { id, name, media = [], variants, description, slug, pricing, ...rest } = productNode const {
id,
name,
media = [],
variants,
description,
slug,
pricing,
...rest
} = productNode
const product = { const product = {
id, id,
name, name,
vendor: '', vendor: '',
description: description ? JSON.parse(description)?.blocks[0]?.data.text : '', description: description
? JSON.parse(description)?.blocks[0]?.data.text
: '',
path: `/${slug}`, path: `/${slug}`,
slug: slug?.replace(/^\/+|\/+$/g, ''), slug: slug?.replace(/^\/+|\/+$/g, ''),
price: (pricing?.priceRange?.start?.net && money(pricing.priceRange.start.net)) || { price: (pricing?.priceRange?.start?.net &&
money(pricing.priceRange.start.net)) || {
value: 0, value: 0,
currencyCode: 'USD', currencyCode: 'USD',
}, },
// TODO: Check nextjs-commerce bug if no images are added for a product // TODO: Check nextjs-commerce bug if no images are added for a product
images: media?.length ? media : [{ url: placeholderImg }], images: media?.length ? media : [{ url: placeholderImg }],
variants: variants && variants.length > 0 ? normalizeProductVariants(variants as ProductVariant[]) : [], variants:
options: variants && variants.length > 0 ? normalizeProductOptions(variants as ProductVariant[]) : [], variants && variants.length > 0
? normalizeProductVariants(variants as ProductVariant[])
: [],
options:
variants && variants.length > 0
? normalizeProductOptions(variants as ProductVariant[])
: [],
...rest, ...rest,
} }
@ -89,7 +117,8 @@ export function normalizeProduct(productNode: SaleorProduct): Product {
export function normalizeCart(checkout: Checkout): Cart { export function normalizeCart(checkout: Checkout): Cart {
const lines = checkout.lines as CheckoutLine[] const lines = checkout.lines as CheckoutLine[]
const lineItems: LineItem[] = lines.length > 0 ? lines?.map<LineItem>(normalizeLineItem) : [] const lineItems: LineItem[] =
lines.length > 0 ? lines?.map<LineItem>(normalizeLineItem) : []
return { return {
id: checkout.id, id: checkout.id,
@ -117,7 +146,7 @@ function normalizeLineItem({ id, variant, quantity }: CheckoutLine): LineItem {
quantity, quantity,
variant: { variant: {
id: String(variant?.id), id: String(variant?.id),
sku: variant?.sku ?? '', ...(variant?.sku && { sku: variant.sku }),
name: variant?.name!, name: variant?.name!,
image: { image: {
url: variant?.media![0] ? variant?.media![0].url : placeholderImg, url: variant?.media![0] ? variant?.media![0].url : placeholderImg,

View File

@ -1,7 +1,11 @@
import * as fragment from '../fragments' import * as fragment from '../fragments'
export const CollectionOne = /* GraphQL */ ` export const CollectionOne = /* GraphQL */ `
query getProductsFromCollection($categoryId: ID!, $first: Int = 100, $channel: String = "default-channel") { query getProductsFromCollection(
$categoryId: ID!
$first: Int = 50
$channel: String = "default-channel"
) {
collection(id: $categoryId, channel: $channel) { collection(id: $categoryId, channel: $channel) {
id id
products(first: $first) { products(first: $first) {

View File

@ -1,5 +1,5 @@
export const getAllProductVendors = /* GraphQL */ ` export const getAllProductVendors = /* GraphQL */ `
query getAllProductVendors($first: Int = 250, $cursor: String) { query getAllProductVendors($first: Int = 50, $cursor: String) {
products(first: $first, after: $cursor) { products(first: $first, after: $cursor) {
pageInfo { pageInfo {
hasNextPage hasNextPage

View File

@ -1,5 +1,9 @@
export const getAllProductsPathsQuery = /* GraphQL */ ` export const getAllProductsPathsQuery = /* GraphQL */ `
query getAllProductPaths($first: Int = 100, $cursor: String, $channel: String = "default-channel") { query getAllProductPaths(
$first: Int = 50
$cursor: String
$channel: String = "default-channel"
) {
products(first: $first, after: $cursor, channel: $channel) { products(first: $first, after: $cursor, channel: $channel) {
pageInfo { pageInfo {
hasNextPage hasNextPage

View File

@ -1,5 +1,5 @@
export const PageMany = /* GraphQL */ ` export const PageMany = /* GraphQL */ `
query PageMany($first: Int = 100) { query PageMany($first: Int = 50) {
pages(first: $first) { pages(first: $first) {
edges { edges {
node { node {

View File

@ -2,12 +2,17 @@ import * as fragment from '../fragments'
export const ProductMany = /* GraphQL */ ` export const ProductMany = /* GraphQL */ `
query ProductMany( query ProductMany(
$first: Int = 100 $first: Int = 50
$filter: ProductFilterInput $filter: ProductFilterInput
$sortBy: ProductOrder $sortBy: ProductOrder
$channel: String = "default-channel" $channel: String = "default-channel"
) { ) {
products(first: $first, channel: $channel, filter: $filter, sortBy: $sortBy) { products(
first: $first
channel: $channel
filter: $filter
sortBy: $sortBy
) {
...ProductConnection ...ProductConnection
} }
} }

View File

@ -19,6 +19,7 @@ export const ProductOneBySlug = /* GraphQL */ `
name name
attributes { attributes {
attribute { attribute {
id
name name
} }
values { values {

View File

@ -1,7 +1,6 @@
import { Product as SFCCProduct, Search } from 'commerce-sdk' import { Product as SFCCProduct, Search } from 'commerce-sdk'
import type { import type {
Product, Product,
ProductImage,
ProductOption, ProductOption,
ProductVariant, ProductVariant,
} from '@vercel/commerce/types/product' } from '@vercel/commerce/types/product'
@ -64,9 +63,9 @@ export function normalizeProduct(
currencyCode: product.currency, currencyCode: product.currency,
}, },
images: product.imageGroups![0].images.map((image) => ({ images: product.imageGroups![0].images.map((image) => ({
url: image.disBaseLink, url: image.disBaseLink || image.link,
altText: image.title, alt: image.title || '',
})) as ProductImage[], })),
variants: normaliseVariants(product.variants), variants: normaliseVariants(product.variants),
options: normaliseOptions(product.variationAttributes), options: normaliseOptions(product.variationAttributes),
} }
@ -87,8 +86,8 @@ export function normalizeSearchProducts(
images: [ images: [
{ {
url: product.image!.link, url: product.image!.link,
altText: product.productName, alt: product.productName,
} as ProductImage, },
], ],
variants: normaliseVariants(product.variants), variants: normaliseVariants(product.variants),
options: normaliseOptions(product.variationAttributes), options: normaliseOptions(product.variationAttributes),

View File

@ -1 +0,0 @@
export default function (_commerce: any) {}

View File

@ -0,0 +1,26 @@
import { ProductsEndpoint } from '.'
const SORT: { [key: string]: string | undefined } = {
latest: 'id',
trending: 'total_sold',
price: 'price',
}
const LIMIT = 12
// Return current cart info
const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
res,
body: { search, categoryId, brandId, sort },
config,
commerce,
}) => {
res.status(200).json({
data: {
products: [],
found: false,
},
})
}
export default getProducts

View File

@ -0,0 +1,19 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import productsEndpoint from '@vercel/commerce/api/endpoints/catalog/products'
import type { ProductsSchema } from '@vercel/commerce/types/product'
import type { ShopifyAPI } from '../../..'
import getProducts from './get-products'
export type ProductsAPI = GetAPISchema<ShopifyAPI, ProductsSchema>
export type ProductsEndpoint = ProductsAPI['endpoint']
export const handlers: ProductsEndpoint['handlers'] = { getProducts }
const productsApi = createEndpoint<ProductsAPI>({
handler: productsEndpoint,
handlers,
})
export default productsApi

View File

@ -13,30 +13,30 @@ import {
export default function getProductOperation({ export default function getProductOperation({
commerce, commerce,
}: OperationContext<Provider>) { }: OperationContext<Provider>) {
async function getProduct(opts: { async function getProduct<T extends GetProductOperation>(opts: {
variables: GetProductOperation['variables'] variables: T['variables']
config?: Partial<ShopifyConfig> config?: Partial<ShopifyConfig>
preview?: boolean preview?: boolean
}): Promise<GetProductOperation['data']> }): Promise<T['data']>
async function getProduct( async function getProduct<T extends GetProductOperation>(
opts: { opts: {
variables: GetProductOperation['variables'] variables: T['variables']
config?: Partial<ShopifyConfig> config?: Partial<ShopifyConfig>
preview?: boolean preview?: boolean
} & OperationOptions } & OperationOptions
): Promise<GetProductOperation['data']> ): Promise<T['data']>
async function getProduct({ async function getProduct<T extends GetProductOperation>({
query = getProductQuery, query = getProductQuery,
variables, variables,
config: cfg, config: cfg,
}: { }: {
query?: string query?: string
variables: GetProductOperation['variables'] variables: T['variables']
config?: Partial<ShopifyConfig> config?: Partial<ShopifyConfig>
preview?: boolean preview?: boolean
}): Promise<GetProductOperation['data']> { }): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(cfg) const { fetch, locale } = commerce.getConfig(cfg)
const { const {

View File

@ -6,7 +6,7 @@ import { GetSiteInfoQueryVariables } from '../../../schema'
import type { ShopifyConfig, Provider } from '..' import type { ShopifyConfig, Provider } from '..'
import { GetSiteInfoOperation } from '../../types/site' import { GetSiteInfoOperation } from '../../types/site'
import { getCategories, getBrands, getSiteInfoQuery } from '../../utils' import { getCategories, getBrands } from '../../utils'
export default function getSiteInfoOperation({ export default function getSiteInfoOperation({
commerce, commerce,
@ -24,9 +24,7 @@ export default function getSiteInfoOperation({
): Promise<T['data']> ): Promise<T['data']>
async function getSiteInfo<T extends GetSiteInfoOperation>({ async function getSiteInfo<T extends GetSiteInfoOperation>({
query = getSiteInfoQuery,
config, config,
variables,
}: { }: {
query?: string query?: string
config?: Partial<ShopifyConfig> config?: Partial<ShopifyConfig>
@ -37,24 +35,15 @@ export default function getSiteInfoOperation({
const categoriesPromise = getCategories(cfg) const categoriesPromise = getCategories(cfg)
const brandsPromise = getBrands(cfg) const brandsPromise = getBrands(cfg)
/*
const { fetch, locale } = cfg const [categories, brands] = await Promise.all([
const { data } = await fetch<GetSiteInfoQuery, GetSiteInfoQueryVariables>( categoriesPromise,
query, brandsPromise,
{ variables }, ])
{
...(locale && {
headers: {
'Accept-Language': locale,
},
}),
}
)
*/
return { return {
categories: await categoriesPromise, categories,
brands: await brandsPromise, brands,
} }
} }

View File

@ -35,7 +35,7 @@ const fetchGraphqlApi: GraphQLFetcher = async (
throw getError( throw getError(
[ [
{ {
message: `${err} \n Most likely related to an unexpected output. e.g the store might be protected with password or not available.`, message: `${err} \n Most likely related to an unexpected output. E.g: NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN & NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN might be incorect.`,
}, },
], ],
500 500

View File

@ -1,5 +1 @@
import * as Core from '@vercel/commerce/types/customer'
export * from '@vercel/commerce/types/customer' export * from '@vercel/commerce/types/customer'
export type CustomerSchema = Core.CustomerSchema

View File

@ -1,8 +1 @@
import * as Core from '@vercel/commerce/types/login'
import type { CustomerAccessTokenCreateInput } from '../../schema'
export * from '@vercel/commerce/types/login' export * from '@vercel/commerce/types/login'
export type LoginOperation = Core.LoginOperation & {
variables: CustomerAccessTokenCreateInput
}

View File

@ -1,11 +1 @@
import * as Core from '@vercel/commerce/types/page'
export * from '@vercel/commerce/types/page' export * from '@vercel/commerce/types/page'
export type Page = Core.Page
export type PageTypes = {
page: Page
}
export type GetAllPagesOperation = Core.GetAllPagesOperation<PageTypes>
export type GetPageOperation = Core.GetPageOperation<PageTypes>

View File

@ -5,19 +5,7 @@ import {
import { ShopifyConfig } from '../api' import { ShopifyConfig } from '../api'
import getAllProductVendors from './queries/get-all-product-vendors-query' import getAllProductVendors from './queries/get-all-product-vendors-query'
export type Brand = { const getBrands = async (config: ShopifyConfig) => {
entityId: string
name: string
path: string
}
export type BrandEdge = {
node: Brand
}
export type Brands = BrandEdge[]
const getBrands = async (config: ShopifyConfig): Promise<BrandEdge[]> => {
const { data } = await config.fetch< const { data } = await config.fetch<
GetAllProductVendorsQuery, GetAllProductVendorsQuery,
GetAllProductVendorsQueryVariables GetAllProductVendorsQueryVariables
@ -32,11 +20,10 @@ const getBrands = async (config: ShopifyConfig): Promise<BrandEdge[]> => {
return [...new Set(vendorsStrings)].map((v) => { return [...new Set(vendorsStrings)].map((v) => {
const id = v.replace(/\s+/g, '-').toLowerCase() const id = v.replace(/\s+/g, '-').toLowerCase()
return { return {
node: { id,
entityId: id, name: v,
name: v, slug: id,
path: `brands/${id}`, path: `/${id}`,
},
} }
}) })
} }

View File

@ -75,7 +75,7 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
return { return {
id, id,
name: title, name: title,
sku: sku ?? id, sku,
price: +priceV2.amount, price: +priceV2.amount,
listPrice: +compareAtPriceV2?.amount, listPrice: +compareAtPriceV2?.amount,
requiresShipping, requiresShipping,
@ -180,6 +180,7 @@ export const normalizePage = (
...page, ...page,
url: `/${locale}/${handle}`, url: `/${locale}/${handle}`,
name, name,
body: page.body ?? '',
}) })
export const normalizePages = (edges: PageEdge[], locale?: string): Page[] => export const normalizePages = (edges: PageEdge[], locale?: string): Page[] =>

View File

@ -120,11 +120,10 @@ export default function getSiteInfoOperation({
.sort(taxonsSort) .sort(taxonsSort)
.map((spreeTaxon: TaxonAttr) => { .map((spreeTaxon: TaxonAttr) => {
return { return {
node: { id: spreeTaxon.id,
entityId: spreeTaxon.id, path: `/${spreeTaxon.id}`,
path: `brands/${spreeTaxon.id}`, slug: spreeTaxon.id,
name: spreeTaxon.attributes.name, name: spreeTaxon.attributes.name,
},
} }
}) })

View File

@ -8,10 +8,9 @@ import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfac
import type { Response } from '@vercel/fetch' import type { Response } from '@vercel/fetch'
import type { ProductOption, Product } from '@vercel/commerce/types/product' import type { ProductOption, Product } from '@vercel/commerce/types/product'
import type { import type {
AddItemHook, Wishlist as CoreWishlist,
RemoveItemHook, WishlistItemBody as CoreWishlistItemBody,
WishlistItemBody, RemoveItemHook as CoreRemoveItemHook,
WishlistTypes,
} from '@vercel/commerce/types/wishlist' } from '@vercel/commerce/types/wishlist'
export type UnknownObjectValues = Record<string, unknown> export type UnknownObjectValues = Record<string, unknown>
@ -134,31 +133,22 @@ export type UserOAuthTokens = {
accessToken: string accessToken: string
} }
// TODO: ExplicitCommerceWishlist is a temporary type export interface Wishlist extends CoreWishlist {
// derived from tsx views. It will be removed once
// Wishlist in @vercel/commerce/types/wishlist is updated
// to a more specific type than `any`.
export type ExplicitCommerceWishlist = {
id: string
token: string token: string
items: {
id: string
product_id: number
variant_id: number
product: Product
}[]
} }
export type ExplicitWishlistAddItemHook = AddItemHook< export interface WishlistItemBody extends CoreWishlistItemBody {
WishlistTypes & { wishlistToken: string
wishlist: ExplicitCommerceWishlist }
itemBody: WishlistItemBody & {
wishlistToken?: string export type AddItemHook = {
} data: Wishlist | null | undefined
} body: { item: WishlistItemBody }
> fetcherInput: { item: WishlistItemBody }
actionInput: WishlistItemBody
export type ExplicitWishlistRemoveItemHook = RemoveItemHook & { }
fetcherInput: { wishlistToken?: string }
body: { wishlistToken?: string } export type RemoveItemHook = CoreRemoveItemHook & {
fetcherInput: { itemId: string; wishlistToken?: string }
body: { temId: string; wishlistToken?: string }
} }

View File

@ -1,6 +1,6 @@
// Based on https://github.com/spark-solutions/spree2vuestorefront/blob/d88d85ae1bcd2ec99b13b81cd2e3c25600a0216e/src/utils/index.ts // Based on https://github.com/spark-solutions/spree2vuestorefront/blob/d88d85ae1bcd2ec99b13b81cd2e3c25600a0216e/src/utils/index.ts
import type { ProductImage } from '@vercel/commerce/types/product' import type { Image } from '@vercel/commerce/types/common'
import type { SpreeProductImage } from '../types' import type { SpreeProductImage } from '../types'
const getMediaGallery = ( const getMediaGallery = (
@ -11,7 +11,7 @@ const getMediaGallery = (
minHeight: number minHeight: number
) => string | null ) => string | null
) => { ) => {
return images.reduce<ProductImage[]>((productImages, _, imageIndex) => { return images.reduce<Image[]>((productImages, _, imageIndex) => {
const url = getImageUrl(images[imageIndex], 800, 800) const url = getImageUrl(images[imageIndex], 800, 800)
if (url) { if (url) {

View File

@ -88,7 +88,6 @@ const normalizeVariant = (
price: parseFloat(spreeVariant.attributes.price), price: parseFloat(spreeVariant.attributes.price),
listPrice: parseFloat(spreeVariant.attributes.price), listPrice: parseFloat(spreeVariant.attributes.price),
image, image,
isInStock: spreeVariant.attributes.in_stock,
availableForSale: spreeVariant.attributes.purchasable, availableForSale: spreeVariant.attributes.purchasable,
...(spreeVariant.attributes.weight === '0.0' ...(spreeVariant.attributes.weight === '0.0'
? {} ? {}

View File

@ -1,9 +1,9 @@
import type { import type {
Product, Product,
ProductImage,
ProductPrice, ProductPrice,
ProductVariant, ProductVariant,
} from '@vercel/commerce/types/product' } from '@vercel/commerce/types/product'
import type { Image } from '@vercel/commerce/types/common'
import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product' import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships' import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
import { jsonApi } from '@spree/storefront-api-v2-sdk' import { jsonApi } from '@spree/storefront-api-v2-sdk'
@ -105,6 +105,7 @@ const normalizeProduct = (
return { return {
id: spreeVariantRecord.id, id: spreeVariantRecord.id,
sku: spreeVariantRecord.attributes.sku || spreeVariantRecord.id,
options: variantOptions, options: variantOptions,
} }
} }
@ -213,7 +214,7 @@ const normalizeProduct = (
createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string) createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string)
) )
const images: ProductImage[] = const images: Image[] =
productImages.length === 0 productImages.length === 0
? placeholderImage === false ? placeholderImage === false
? [] ? []

View File

@ -4,59 +4,54 @@ import { jsonApi } from '@spree/storefront-api-v2-sdk'
import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product' import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
import type { WishedItemAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/WishedItem' import type { WishedItemAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/WishedItem'
import type { WishlistAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Wishlist' import type { WishlistAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Wishlist'
import type { import type { SpreeSdkResponse, VariantAttr } from '../../types'
ExplicitCommerceWishlist,
SpreeSdkResponse,
VariantAttr,
} from '../../types'
import normalizeProduct from './normalize-product' import normalizeProduct from './normalize-product'
import { Wishlist } from '@vercel/commerce/types/wishlist'
const normalizeWishlist = ( const normalizeWishlist = (
spreeSuccessResponse: SpreeSdkResponse, spreeSuccessResponse: SpreeSdkResponse,
spreeWishlist: WishlistAttr spreeWishlist: WishlistAttr
): ExplicitCommerceWishlist => { ): Wishlist => {
const spreeWishedItems = jsonApi.findRelationshipDocuments<WishedItemAttr>( const spreeWishedItems = jsonApi.findRelationshipDocuments<WishedItemAttr>(
spreeSuccessResponse, spreeSuccessResponse,
spreeWishlist, spreeWishlist,
'wished_items' 'wished_items'
) )
const items: ExplicitCommerceWishlist['items'] = spreeWishedItems.map( const items: Wishlist['items'] = spreeWishedItems.map((spreeWishedItem) => {
(spreeWishedItem) => { const spreeWishedVariant =
const spreeWishedVariant = jsonApi.findSingleRelationshipDocument<VariantAttr>(
jsonApi.findSingleRelationshipDocument<VariantAttr>( spreeSuccessResponse,
spreeSuccessResponse, spreeWishedItem,
spreeWishedItem, 'variant'
'variant' )
)
if (spreeWishedVariant === null) { if (spreeWishedVariant === null) {
throw new MissingVariantError( throw new MissingVariantError(
`Couldn't find variant for wished item with id ${spreeWishedItem.id}.` `Couldn't find variant for wished item with id ${spreeWishedItem.id}.`
) )
}
const spreeWishedProduct =
jsonApi.findSingleRelationshipDocument<ProductAttr>(
spreeSuccessResponse,
spreeWishedVariant,
'product'
)
if (spreeWishedProduct === null) {
throw new MissingProductError(
`Couldn't find product for variant with id ${spreeWishedVariant.id}.`
)
}
return {
id: spreeWishedItem.id,
product_id: parseInt(spreeWishedProduct.id, 10),
variant_id: parseInt(spreeWishedVariant.id, 10),
product: normalizeProduct(spreeSuccessResponse, spreeWishedProduct),
}
} }
)
const spreeWishedProduct =
jsonApi.findSingleRelationshipDocument<ProductAttr>(
spreeSuccessResponse,
spreeWishedVariant,
'product'
)
if (spreeWishedProduct === null) {
throw new MissingProductError(
`Couldn't find product for variant with id ${spreeWishedVariant.id}.`
)
}
return {
id: spreeWishedItem.id,
productId: spreeWishedProduct.id,
variantId: spreeWishedVariant.id,
product: normalizeProduct(spreeSuccessResponse, spreeWishedProduct),
}
})
return { return {
id: spreeWishlist.id, id: spreeWishlist.id,

View File

@ -2,8 +2,9 @@ import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types' import type { MutationHook } from '@vercel/commerce/utils/types'
import useAddItem from '@vercel/commerce/wishlist/use-add-item' import useAddItem from '@vercel/commerce/wishlist/use-add-item'
import type { UseAddItem } from '@vercel/commerce/wishlist/use-add-item' import type { UseAddItem } from '@vercel/commerce/wishlist/use-add-item'
import type { AddItemHook } from '@vercel/commerce/types/wishlist'
import useWishlist from './use-wishlist' import useWishlist from './use-wishlist'
import type { ExplicitWishlistAddItemHook } from '../types'
import type { import type {
WishedItem, WishedItem,
WishlistsAddWishedItem, WishlistsAddWishedItem,
@ -11,12 +12,12 @@ import type {
import type { GraphQLFetcherResult } from '@vercel/commerce/api' import type { GraphQLFetcherResult } from '@vercel/commerce/api'
import ensureIToken from '../utils/tokens/ensure-itoken' import ensureIToken from '../utils/tokens/ensure-itoken'
import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token' import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
import type { AddItemHook } from '@vercel/commerce/types/wishlist'
import isLoggedIn from '../utils/tokens/is-logged-in' import isLoggedIn from '../utils/tokens/is-logged-in'
export default useAddItem as UseAddItem<typeof handler> export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<ExplicitWishlistAddItemHook> = { export const handler: MutationHook<AddItemHook> = {
fetchOptions: { fetchOptions: {
url: 'wishlists', url: 'wishlists',
query: 'addWishedItem', query: 'addWishedItem',
@ -31,7 +32,7 @@ export const handler: MutationHook<ExplicitWishlistAddItemHook> = {
) )
const { const {
item: { productId, variantId, wishlistToken }, item: { variantId, wishlistToken },
} = input } = input
if (!isLoggedIn() || !wishlistToken) { if (!isLoggedIn() || !wishlistToken) {

View File

@ -2,8 +2,8 @@ import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types' import type { MutationHook } from '@vercel/commerce/utils/types'
import useRemoveItem from '@vercel/commerce/wishlist/use-remove-item' import useRemoveItem from '@vercel/commerce/wishlist/use-remove-item'
import type { UseRemoveItem } from '@vercel/commerce/wishlist/use-remove-item' import type { UseRemoveItem } from '@vercel/commerce/wishlist/use-remove-item'
import type { RemoveItemHook } from '@vercel/commerce/types/wishlist'
import useWishlist from './use-wishlist' import useWishlist from './use-wishlist'
import type { ExplicitWishlistRemoveItemHook } from '../types'
import isLoggedIn from '../utils/tokens/is-logged-in' import isLoggedIn from '../utils/tokens/is-logged-in'
import ensureIToken from '../utils/tokens/ensure-itoken' import ensureIToken from '../utils/tokens/ensure-itoken'
import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token' import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
@ -12,7 +12,7 @@ import type { WishedItem } from '@spree/storefront-api-v2-sdk/types/interfaces/W
export default useRemoveItem as UseRemoveItem<typeof handler> export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler: MutationHook<ExplicitWishlistRemoveItemHook> = { export const handler: MutationHook<RemoveItemHook> = {
fetchOptions: { fetchOptions: {
url: 'wishlists', url: 'wishlists',
query: 'removeWishedItem', query: 'removeWishedItem',
@ -45,7 +45,7 @@ export const handler: MutationHook<ExplicitWishlistRemoveItemHook> = {
}, },
useHook: ({ fetch }) => { useHook: ({ fetch }) => {
const useWrappedHook: ReturnType< const useWrappedHook: ReturnType<
MutationHook<ExplicitWishlistRemoveItemHook>['useHook'] MutationHook<RemoveItemHook>['useHook']
> = () => { > = () => {
const wishlist = useWishlist() const wishlist = useWishlist()

View File

@ -9,6 +9,7 @@ import type { Wishlist } from '@spree/storefront-api-v2-sdk/types/interfaces/Wis
import ensureIToken from '../utils/tokens/ensure-itoken' import ensureIToken from '../utils/tokens/ensure-itoken'
import normalizeWishlist from '../utils/normalizations/normalize-wishlist' import normalizeWishlist from '../utils/normalizations/normalize-wishlist'
import isLoggedIn from '../utils/tokens/is-logged-in' import isLoggedIn from '../utils/tokens/is-logged-in'
import { ValidationError } from '@vercel/commerce/utils/errors'
export default useWishlist as UseWishlist<typeof handler> export default useWishlist as UseWishlist<typeof handler>
@ -28,7 +29,9 @@ export const handler: SWRHook<GetWishlistHook> = {
) )
if (!isLoggedIn()) { if (!isLoggedIn()) {
return null throw new ValidationError({
message: 'Not logged in',
})
} }
// TODO: Optimize with includeProducts. // TODO: Optimize with includeProducts.

Some files were not shown because too many files have changed in this diff Show More