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
/**
* 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
custom_url?: definitions['customUrl_Full']

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import type { WishlistItemBody } from '../../types/wishlist'
import type { CartItemBody, OptionSelections } from '../../types/cart'
import type { CartItemBody, SelectedOption } from '../../types/cart'
type BCWishlistItemBody = {
product_id: number
@ -10,7 +10,7 @@ type BCCartItemBody = {
product_id: number
variant_id: number
quantity?: number
option_selections?: OptionSelections[]
option_selections?: SelectedOption[]
}
export const parseWishlistItem = (
@ -24,5 +24,5 @@ export const parseCartItem = (item: CartItemBody): BCCartItemBody => ({
quantity: item.quantity,
product_id: Number(item.productId),
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 { Cart, BigcommerceCart, LineItem } from '../types/cart'
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 update from './immutability'
import getSlug from './get-slug'
@ -54,7 +54,7 @@ export function normalizeProduct(productNode: any): Product {
: [],
},
brand: {
$apply: (brand: any) => (brand?.entityId ? brand?.entityId : null),
$apply: (brand: any) => (brand?.id ? brand.id : null),
},
slug: {
$set: path?.replace(/^\/+|\/+$/g, ''),
@ -134,3 +134,12 @@ export function normalizeCategory(category: BCCategory): Category {
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'
// TODO: this type should match:
@ -24,43 +22,3 @@ export type BigcommerceCart = {
discounts?: { id: number; discounted_amount: number }[]
// 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 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 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 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, GetSiteInfoQueryVariables } from '../../schema'
import type { GetSiteInfoQuery } from '../../schema'
export * from '@vercel/commerce/types/site'
@ -7,13 +6,6 @@ export type BCCategory = NonNullable<
GetSiteInfoQuery['site']['categoryTree']
>[0]
export type Brand = NonNullable<
export type BCBrand = NonNullable<
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 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,8 +3,10 @@ import { CommerceAPIError } from '../utils/errors'
import isAllowedOperation from '../utils/is-allowed-operation'
import type { GetAPISchema } from '..'
const cartEndpoint: GetAPISchema<any, CartSchema<any>>['endpoint']['handler'] =
async (ctx) => {
const cartEndpoint: GetAPISchema<
any,
CartSchema
>['endpoint']['handler'] = async (ctx) => {
const { req, res, handlers, config } = ctx
if (
@ -55,6 +57,6 @@ const cartEndpoint: GetAPISchema<any, CartSchema<any>>['endpoint']['handler'] =
res.status(500).json({ data: null, errors: [{ message }] })
}
}
}
export default cartEndpoint

View File

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

View File

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

View File

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

View File

@ -1,4 +1,7 @@
import { ZodError } from 'zod'
import type { Response } from '@vercel/fetch'
import { CommerceError } from '../../utils/errors'
export class CommerceAPIError extends Error {
status: number
@ -20,3 +23,38 @@ export class CommerceNetworkError extends Error {
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 { 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 =
(
operation: AllowedOperations,
@ -16,27 +17,7 @@ export const withSchemaParser =
parse(operation, result)
return result
} catch (error) {
if (error instanceof z.ZodError) {
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)
}
return Promise.reject(getOperationError(operation, error))
}
}
@ -47,11 +28,26 @@ const parse = (operation: AllowedOperations, data: OperationsData) => {
break
case 'getAllProducts':
data.products?.forEach((product: any) => productSchema.parse(product))
z.array(productSchema).parse(data.products)
break
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
}
}

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import { useHook, useSWRHook } from '../utils/use-hook'
import { Provider, useCommerce } from '..'
export type UseCheckout<
H extends SWRHook<GetCheckoutHook<any>> = SWRHook<GetCheckoutHook>
H extends SWRHook<GetCheckoutHook> = SWRHook<GetCheckoutHook>
> = ReturnType<H['useHook']>
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'
export type UseSubmitCheckout<
H extends MutationHook<
SubmitCheckoutHook<any>
> = MutationHook<SubmitCheckoutHook>
H extends MutationHook<SubmitCheckoutHook> = MutationHook<SubmitCheckoutHook>
> = ReturnType<H['useHook']>
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({
value: z.number(),
currencyCode: z.string().optional(),
currencyCode: z.string().max(3).optional(),
retailPrice: z.number().optional(),
})
@ -18,7 +18,7 @@ export const productOptionSchema = z.object({
})
export const productImageSchema = z.object({
url: z.string(),
url: z.string().url().or(z.string().startsWith('/')),
alt: z.string().optional(),
width: z.number().optional(),
height: z.number().optional(),
@ -26,7 +26,7 @@ export const productImageSchema = z.object({
export const productVariantSchema = z.object({
id: z.string(),
sku: z.string(),
sku: z.string().optional(),
name: z.string(),
options: z.array(productOptionSchema),
image: productImageSchema.optional(),
@ -46,3 +46,15 @@ export const productSchema = z.object({
options: z.array(productOptionSchema),
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`
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.
*/
sku: string
sku?: string
/**
* The product variants name, or the product's name.
*/
@ -19,7 +19,7 @@ export interface ProductVariant {
*/
price: number
/**
* Product variants price, as quoted by the manufacturer/distributor.
* The product variants price before discounts are applied.
*/
listPrice: number
/**
@ -39,38 +39,38 @@ export interface ProductVariant {
export interface SelectedOption {
/**
* The selected option's id
* The unique identifier for the option.
*/
id?: string
/**
* The product options name.
* The product options name, such as "Color" or "Size".
*/
name: string
/**
* The product options value.
* The product options value, such as "Red" or "XL".
*/
value: string
}
export interface LineItem {
/**
* The line item's id.
* The unique identifier for the line item.
*/
id: string
/**
* The product variants id.
* The unique identifier for the product variant.
*/
variantId: string
/**
* The product's id.
* The unique identifier for the product, if the variant is not provided.
*/
productId: string
/**
* The name of the line item.
* This is usually the product's name.
*/
name: string
/**
* List of discounts applied to the line item.
* The quantity of the product variant in the line item.
*/
quantity: number
/**
@ -86,7 +86,7 @@ export interface LineItem {
*/
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[]
}
@ -96,7 +96,7 @@ export interface LineItem {
*/
export interface Cart {
/**
* The cart's id.
* The unique identifier for the cart.
*/
id: string
/**
@ -119,7 +119,7 @@ export interface Cart {
* The currency used for this cart */
currency: { code: string }
/**
* Specifies if taxes are included in the line items.
* Indicates if taxes are included in the line items.
*/
taxesIncluded: boolean
/**
@ -135,12 +135,13 @@ export interface Cart {
* Price of the cart before duties, shipping and taxes.*/
subtotalPrice: number
/**
* The sum of all the prices of all the items in the cart.*/
/**
* Duties, taxes and discounts included.*/
* The sum of all the prices of all the items in the cart.
* Duties, taxes and discounts included.
*/
totalPrice: number
/**
* Discounts that have been applied on the cart.*/
* Discounts that have been applied on the cart.
*/
discounts?: Discount[]
}
@ -149,17 +150,22 @@ export interface Cart {
*/
export interface CartItemBody {
/**
* The product variant's id.
* The unique identifier for the product variant.
*/
variantId: string
/**
* The product's id.
* The unique identifier for the product, if the variant is not provided.
*/
productId?: string
/**
* The quantity of the product variant.
*/
quantity?: number
/**
* The product variant's selected options.
*/
optionsSelected?: SelectedOption[]
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,28 +1,43 @@
// TODO: define this type
export type Page = {
// ID of the Web page.
/**
* The unique identifier for the page.
*/
id: string
// Page name, as displayed on the storefront.
/**
* Page name, as displayed on the storefront.
*/
name: string
// Relative URL on the storefront for this page.
/**
* Relative URL on the storefront for this page.
*/
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
// If true, this page appears in the storefronts navigation menu.
/**
* If true, this page appears in the storefronts navigation menu.
*/
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
}
export type PageTypes = {
page: Page
/**
* Operation to get all pages.
*/
export type GetAllPagesOperation = {
data: { pages: Page[] }
}
export type GetAllPagesOperation<T extends PageTypes = PageTypes> = {
data: { pages: T['page'][] }
}
export type GetPageOperation<T extends PageTypes = PageTypes> = {
data: { page?: T['page'] }
variables: { id: string }
export type GetPageOperation = {
data: { page?: Page }
variables: {
/**
* The unique identifier of the page.
*/
id: string
}
}

View File

@ -6,7 +6,7 @@ export interface ProductPrice {
*/
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
/**
@ -18,11 +18,11 @@ export interface ProductPrice {
export interface ProductOption {
__typename?: 'MultipleChoiceOption'
/**
* The option's id.
* The unique identifier for the option.
*/
id: string
/**
* The option's name.
* The product options name.
*/
displayName: string
/**
@ -44,17 +44,17 @@ export interface ProductOptionValues {
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.
*/
sku: string
sku?: string
/**
* The product variants name, or the product's name.
*/
name: string
name?: string
/**
* List of product options.
*/
@ -62,11 +62,11 @@ export interface ProductVariant {
/**
* 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.
*/
@ -83,11 +83,11 @@ export interface ProductVariant {
export interface Product {
/**
* The product's id.
* The unique identifier for the product.
*/
id: string
/**
* The product's name.
* The name of the product.
*/
name: string
/**
@ -107,7 +107,7 @@ export interface Product {
*/
slug?: string
/**
* A human-friendly string for the product, containing U.
* Relative URL on the storefront for the product.
*/
path?: string
/**
@ -123,7 +123,7 @@ export interface Product {
*/
price: ProductPrice
/**
* The product's price.
* List of product's options.
*/
options: ProductOption[]
/**
@ -140,13 +140,14 @@ export interface SearchProductsBody {
/**
* The category ID to filter the products by.
*/
categoryId?: string | number
categoryId?: string
/**
* 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
/**

View File

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

View File

@ -1,20 +1,51 @@
export type Category = {
/**
* Unique identifier for the category.
*/
id: string
/**
* Name of the category.
*/
name: string
/**
* A human-friendly unique string for the category, automatically generated from its name.
* @example "t-shirts"
*/
slug: string
/**
* Relative URL on the storefront for the category.
* @example /t-shirts
*/
path: string
}
export type Brand = any
export type SiteTypes = {
category: Category
brand: Brand
export type Brand = {
/**
* Unique identifier for the 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: {
categories: T['category'][]
brands: T['brand'][]
categories: Category[]
brands: Brand[]
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1 @@
import { LoginBody, LoginTypes } 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 []
return variants?.map((variant) => ({
id: variant.id,
sku: variant.sku ?? variant.id,
options: Object.entries(variant.options).map(
([variantGroupId, variantOptionId]) => {
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'
export type Wishlist = {
items: [
{
product_id: number
variant_id: number
id: number
product: any
}
]
}
export interface UseWishlistOptions {
includeProducts?: boolean
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import * as Core from '@vercel/commerce/types/page'
export * from '@vercel/commerce/types/page'
export type Maybe<T> = T | null
export type Scalars = {
ID: string
@ -13,23 +14,3 @@ export type Scalars = {
/** Object custom scalar type */
Object: any
}
export * from '@vercel/commerce/types/page'
export type Page = Core.Page
export type PageTypes = {
page: Page
}
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 type { OperationContext } from '@vercel/commerce/api/operations'
export default function getProductOperation({
commerce,
}: OperationContext<any>) {
export default function getProductOperation(_p: OperationContext<any>) {
async function getProduct<T extends GetProductOperation>({
query = '',
variables,

View File

@ -1,5 +1,5 @@
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'
export type GetSiteInfoResult<

View File

@ -7,7 +7,8 @@
"path": "/new-short-sleeve-t-shirt",
"slug": "new-short-sleeve-t-shirt",
"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": [
{
"url": "/assets/drop-shirt-0.png",
@ -31,6 +32,8 @@
"variants": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"name": "New Short Sleeve T-Shirt",
"sku": "new-short-sleeve-t-shirt",
"options": [
{
"__typename": "MultipleChoiceOption",
@ -80,6 +83,7 @@
"path": "/lightweight-jacket",
"slug": "lightweight-jacket",
"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>",
"images": [
{
@ -104,6 +108,8 @@
"variants": [
{
"id": "Z2lkOid8vc2hvcGlmeS9Qcm9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"name": "Lightweight Jacket",
"sku": "lightweight-jacket",
"options": [
{
"__typename": "MultipleChoiceOption",
@ -153,6 +159,7 @@
"path": "/shirt",
"slug": "shirt",
"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>",
"images": [
{
@ -189,6 +196,8 @@
"variants": [
{
"id": "Z2lkOi8vc2hvcGlmeS9Qcms9kdWN0LzU0NDczMjUwMjQ0MjAss=",
"name": "Shirt",
"sku": "shirt",
"options": [
{
"__typename": "MultipleChoiceOption",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,8 @@
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 = {}
export type Wishlist = {
items: [
{
product_id: number
variant_id: number
id: number
product: Product
}
]
}
export interface UseWishlistOptions {
includeProducts?: boolean
}

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,9 @@ type ReturnType = {
product: any
}
export default function getProductOperation({ commerce }: OperationContext<Provider>) {
export default function getProductOperation({
commerce,
}: OperationContext<Provider>) {
async function getProduct({
query = Query.ProductOneBySlug,
variables,
@ -38,7 +40,7 @@ export default function getProductOperation({ commerce }: OperationContext<Provi
)
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
if (categoryId) {
edges = data.collection?.products?.edges ?? []
edges = data?.collection?.products?.edges ?? []
// FIXME @zaiste, no `vendor` in Saleor
// if (brandId) {
// edges = edges.filter(
@ -47,11 +47,13 @@ export const handler: SWRHook<SearchProductsHook> = {
// )
// }
} else {
edges = data.products?.edges ?? []
edges = data?.products?.edges ?? []
}
return {
products: edges.map(({ node }: ProductCountableEdge) => normalizeProduct(node)),
products: edges.map(({ node }: ProductCountableEdge) =>
normalizeProduct(node)
),
found: !!edges.length,
}
},

View File

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

View File

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

View File

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

View File

@ -1,7 +1,11 @@
import * as fragment from '../fragments'
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) {
id
products(first: $first) {

View File

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

View File

@ -1,5 +1,9 @@
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) {
pageInfo {
hasNextPage

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
import { Product as SFCCProduct, Search } from 'commerce-sdk'
import type {
Product,
ProductImage,
ProductOption,
ProductVariant,
} from '@vercel/commerce/types/product'
@ -64,9 +63,9 @@ export function normalizeProduct(
currencyCode: product.currency,
},
images: product.imageGroups![0].images.map((image) => ({
url: image.disBaseLink,
altText: image.title,
})) as ProductImage[],
url: image.disBaseLink || image.link,
alt: image.title || '',
})),
variants: normaliseVariants(product.variants),
options: normaliseOptions(product.variationAttributes),
}
@ -87,8 +86,8 @@ export function normalizeSearchProducts(
images: [
{
url: product.image!.link,
altText: product.productName,
} as ProductImage,
alt: product.productName,
},
],
variants: normaliseVariants(product.variants),
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({
commerce,
}: OperationContext<Provider>) {
async function getProduct(opts: {
variables: GetProductOperation['variables']
async function getProduct<T extends GetProductOperation>(opts: {
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<GetProductOperation['data']>
}): Promise<T['data']>
async function getProduct(
async function getProduct<T extends GetProductOperation>(
opts: {
variables: GetProductOperation['variables']
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
} & OperationOptions
): Promise<GetProductOperation['data']>
): Promise<T['data']>
async function getProduct({
async function getProduct<T extends GetProductOperation>({
query = getProductQuery,
variables,
config: cfg,
}: {
query?: string
variables: GetProductOperation['variables']
variables: T['variables']
config?: Partial<ShopifyConfig>
preview?: boolean
}): Promise<GetProductOperation['data']> {
}): Promise<T['data']> {
const { fetch, locale } = commerce.getConfig(cfg)
const {

View File

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

View File

@ -35,7 +35,7 @@ const fetchGraphqlApi: GraphQLFetcher = async (
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

View File

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

View File

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

View File

@ -120,11 +120,10 @@ export default function getSiteInfoOperation({
.sort(taxonsSort)
.map((spreeTaxon: TaxonAttr) => {
return {
node: {
entityId: spreeTaxon.id,
path: `brands/${spreeTaxon.id}`,
id: spreeTaxon.id,
path: `/${spreeTaxon.id}`,
slug: spreeTaxon.id,
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 { ProductOption, Product } from '@vercel/commerce/types/product'
import type {
AddItemHook,
RemoveItemHook,
WishlistItemBody,
WishlistTypes,
Wishlist as CoreWishlist,
WishlistItemBody as CoreWishlistItemBody,
RemoveItemHook as CoreRemoveItemHook,
} from '@vercel/commerce/types/wishlist'
export type UnknownObjectValues = Record<string, unknown>
@ -134,31 +133,22 @@ export type UserOAuthTokens = {
accessToken: string
}
// TODO: ExplicitCommerceWishlist is a temporary type
// 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
export interface Wishlist extends CoreWishlist {
token: string
items: {
id: string
product_id: number
variant_id: number
product: Product
}[]
}
export type ExplicitWishlistAddItemHook = AddItemHook<
WishlistTypes & {
wishlist: ExplicitCommerceWishlist
itemBody: WishlistItemBody & {
wishlistToken?: string
}
}
>
export type ExplicitWishlistRemoveItemHook = RemoveItemHook & {
fetcherInput: { wishlistToken?: string }
body: { wishlistToken?: string }
export interface WishlistItemBody extends CoreWishlistItemBody {
wishlistToken: string
}
export type AddItemHook = {
data: Wishlist | null | undefined
body: { item: WishlistItemBody }
fetcherInput: { item: WishlistItemBody }
actionInput: WishlistItemBody
}
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
import type { ProductImage } from '@vercel/commerce/types/product'
import type { Image } from '@vercel/commerce/types/common'
import type { SpreeProductImage } from '../types'
const getMediaGallery = (
@ -11,7 +11,7 @@ const getMediaGallery = (
minHeight: number
) => string | null
) => {
return images.reduce<ProductImage[]>((productImages, _, imageIndex) => {
return images.reduce<Image[]>((productImages, _, imageIndex) => {
const url = getImageUrl(images[imageIndex], 800, 800)
if (url) {

View File

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

View File

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

View File

@ -4,25 +4,21 @@ import { jsonApi } from '@spree/storefront-api-v2-sdk'
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 { WishlistAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Wishlist'
import type {
ExplicitCommerceWishlist,
SpreeSdkResponse,
VariantAttr,
} from '../../types'
import type { SpreeSdkResponse, VariantAttr } from '../../types'
import normalizeProduct from './normalize-product'
import { Wishlist } from '@vercel/commerce/types/wishlist'
const normalizeWishlist = (
spreeSuccessResponse: SpreeSdkResponse,
spreeWishlist: WishlistAttr
): ExplicitCommerceWishlist => {
): Wishlist => {
const spreeWishedItems = jsonApi.findRelationshipDocuments<WishedItemAttr>(
spreeSuccessResponse,
spreeWishlist,
'wished_items'
)
const items: ExplicitCommerceWishlist['items'] = spreeWishedItems.map(
(spreeWishedItem) => {
const items: Wishlist['items'] = spreeWishedItems.map((spreeWishedItem) => {
const spreeWishedVariant =
jsonApi.findSingleRelationshipDocument<VariantAttr>(
spreeSuccessResponse,
@ -51,12 +47,11 @@ const normalizeWishlist = (
return {
id: spreeWishedItem.id,
product_id: parseInt(spreeWishedProduct.id, 10),
variant_id: parseInt(spreeWishedVariant.id, 10),
productId: spreeWishedProduct.id,
variantId: spreeWishedVariant.id,
product: normalizeProduct(spreeSuccessResponse, spreeWishedProduct),
}
}
)
})
return {
id: spreeWishlist.id,

View File

@ -2,8 +2,9 @@ import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types'
import 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 type { ExplicitWishlistAddItemHook } from '../types'
import type {
WishedItem,
WishlistsAddWishedItem,
@ -11,12 +12,12 @@ import type {
import type { GraphQLFetcherResult } from '@vercel/commerce/api'
import ensureIToken from '../utils/tokens/ensure-itoken'
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'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<ExplicitWishlistAddItemHook> = {
export const handler: MutationHook<AddItemHook> = {
fetchOptions: {
url: 'wishlists',
query: 'addWishedItem',
@ -31,7 +32,7 @@ export const handler: MutationHook<ExplicitWishlistAddItemHook> = {
)
const {
item: { productId, variantId, wishlistToken },
item: { variantId, wishlistToken },
} = input
if (!isLoggedIn() || !wishlistToken) {

View File

@ -2,8 +2,8 @@ import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types'
import 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 type { ExplicitWishlistRemoveItemHook } from '../types'
import isLoggedIn from '../utils/tokens/is-logged-in'
import ensureIToken from '../utils/tokens/ensure-itoken'
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 const handler: MutationHook<ExplicitWishlistRemoveItemHook> = {
export const handler: MutationHook<RemoveItemHook> = {
fetchOptions: {
url: 'wishlists',
query: 'removeWishedItem',
@ -45,7 +45,7 @@ export const handler: MutationHook<ExplicitWishlistRemoveItemHook> = {
},
useHook: ({ fetch }) => {
const useWrappedHook: ReturnType<
MutationHook<ExplicitWishlistRemoveItemHook>['useHook']
MutationHook<RemoveItemHook>['useHook']
> = () => {
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 normalizeWishlist from '../utils/normalizations/normalize-wishlist'
import isLoggedIn from '../utils/tokens/is-logged-in'
import { ValidationError } from '@vercel/commerce/utils/errors'
export default useWishlist as UseWishlist<typeof handler>
@ -28,7 +29,9 @@ export const handler: SWRHook<GetWishlistHook> = {
)
if (!isLoggedIn()) {
return null
throw new ValidationError({
message: 'Not logged in',
})
}
// TODO: Optimize with includeProducts.

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