mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Update types (#831)
* Update product types * Cart types progress, add zod & initial schema validator * Update normalize.ts * Update with-schema-parser.ts * Updated types, schemas & providers * Fix providers after schema parse errors * Fix paths * More provider fixes * Fix kibocommerce & commercejs * Add customer updated types & fixes * Add checkout & customer types * Import core types only from commerce * Update tsconfig.json * Convert hooks interfaces to types * Requested changes * Change to relative paths * Move Zod dependency
This commit is contained in:
@@ -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,
|
||||
url: `/${locale}/${slug}`,
|
||||
name,
|
||||
}))
|
||||
const pages =
|
||||
data?.pages?.edges?.map(
|
||||
({ node: { title: name, slug, ...node } }: PageCountableEdge) => ({
|
||||
id: node.id,
|
||||
url: `/${locale}/${slug}`,
|
||||
body: node.content || '',
|
||||
name,
|
||||
})
|
||||
) ?? []
|
||||
|
||||
return { pages }
|
||||
}
|
||||
|
@@ -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) => ({
|
||||
path: `/${slug}`,
|
||||
})),
|
||||
products: data?.products?.edges?.map(
|
||||
({ node: { slug } }: ProductCountableEdge) =>
|
||||
({
|
||||
path: `/${slug}`,
|
||||
} ?? [])
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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,13 +36,15 @@ export default function getPageOperation({ commerce }: OperationContext<Provider
|
||||
)
|
||||
|
||||
return {
|
||||
page: page
|
||||
? {
|
||||
...page,
|
||||
name: page.title,
|
||||
url: `/${locale}/${page.slug}`,
|
||||
}
|
||||
: null,
|
||||
page:
|
||||
data && data.page
|
||||
? {
|
||||
...data.page,
|
||||
name: data.page.title,
|
||||
body: data.page.content || '',
|
||||
url: `/${locale}/${data.page.slug}`,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
@@ -11,7 +11,7 @@ import useCart from './use-cart'
|
||||
import * as mutation from '../utils/mutations'
|
||||
import { getCheckoutId, checkoutToCart } from '../utils'
|
||||
import { Mutation, MutationCheckoutLineDeleteArgs } from '../../schema'
|
||||
import { LineItem, RemoveItemHook } from '../types/cart'
|
||||
import type { LineItem, RemoveItemHook } from '@vercel/commerce/types/cart'
|
||||
|
||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
|
@@ -1,19 +1,23 @@
|
||||
import { useCallback } from 'react'
|
||||
import debounce from 'lodash.debounce'
|
||||
import type { HookFetcherContext, MutationHookContext } from '@vercel/commerce/utils/types'
|
||||
import type {
|
||||
HookFetcherContext,
|
||||
MutationHookContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import { ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useUpdateItem, { UseUpdateItem } from '@vercel/commerce/cart/use-update-item'
|
||||
import useUpdateItem, {
|
||||
UseUpdateItem,
|
||||
} from '@vercel/commerce/cart/use-update-item'
|
||||
|
||||
import useCart from './use-cart'
|
||||
import { handler as removeItemHandler } from './use-remove-item'
|
||||
import type { LineItem } from '../types'
|
||||
import { checkoutToCart } from '../utils'
|
||||
import { getCheckoutId } from '../utils'
|
||||
import { Mutation, MutationCheckoutLinesUpdateArgs } from '../../schema'
|
||||
|
||||
import * as mutation from '../utils/mutations'
|
||||
|
||||
import type { UpdateItemHook } from '../types/cart'
|
||||
import type { UpdateItemHook, LineItem } from '@vercel/commerce/types/cart'
|
||||
import type { Mutation, MutationCheckoutLinesUpdateArgs } from '../../schema'
|
||||
|
||||
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<UpdateItemHook['actionInput']>
|
||||
@@ -23,7 +27,11 @@ export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: { query: mutation.CheckoutLineUpdate },
|
||||
async fetcher({ input: { itemId, item }, options, fetch }: HookFetcherContext<UpdateItemHook>) {
|
||||
async fetcher({
|
||||
input: { itemId, item },
|
||||
options,
|
||||
fetch,
|
||||
}: HookFetcherContext<UpdateItemHook>) {
|
||||
if (Number.isInteger(item.quantity)) {
|
||||
// Also allow the update hook to remove an item if the quantity is lower than 1
|
||||
if (item.quantity! < 1) {
|
||||
@@ -40,7 +48,10 @@ export const handler = {
|
||||
}
|
||||
|
||||
const checkoutId = getCheckoutId().checkoutId
|
||||
const { checkoutLinesUpdate } = await fetch<Mutation, MutationCheckoutLinesUpdateArgs>({
|
||||
const { checkoutLinesUpdate } = await fetch<
|
||||
Mutation,
|
||||
MutationCheckoutLinesUpdateArgs
|
||||
>({
|
||||
...options,
|
||||
variables: {
|
||||
checkoutId,
|
||||
|
@@ -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,
|
||||
}
|
||||
},
|
||||
|
@@ -1,32 +0,0 @@
|
||||
import * as Core from '@vercel/commerce/types/cart'
|
||||
|
||||
export * from '@vercel/commerce/types/cart'
|
||||
|
||||
export type SaleorCart = {}
|
||||
|
||||
/**
|
||||
* 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']
|
@@ -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) => ({
|
||||
id,
|
||||
name,
|
||||
slug,
|
||||
path: `/${slug}`,
|
||||
})) ?? []
|
||||
data?.collections?.edges?.map(
|
||||
({ node: { id, name, slug } }: CollectionCountableEdge) => ({
|
||||
id,
|
||||
name,
|
||||
slug,
|
||||
path: `/${slug}`,
|
||||
})
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -30,7 +30,7 @@ const getVendors = async (config: SaleorConfig): Promise<BrandEdge[]> => {
|
||||
// node: {
|
||||
// entityId: id,
|
||||
// name: v,
|
||||
// path: `brands/${id}`,
|
||||
// path: `/${id}`,
|
||||
// },
|
||||
// }
|
||||
// })
|
||||
|
@@ -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,
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export const PageMany = /* GraphQL */ `
|
||||
query PageMany($first: Int = 100) {
|
||||
query PageMany($first: Int = 50) {
|
||||
pages(first: $first) {
|
||||
edges {
|
||||
node {
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ export const ProductOneBySlug = /* GraphQL */ `
|
||||
name
|
||||
attributes {
|
||||
attribute {
|
||||
id
|
||||
name
|
||||
}
|
||||
values {
|
||||
|
Reference in New Issue
Block a user