mirror of
https://github.com/vercel/commerce.git
synced 2025-07-04 12:11:22 +00:00
Enable text search for the Spree Framework
This commit is contained in:
parent
744a8b998e
commit
a27996a088
@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
COMMERCE_PROVIDER=spree
|
COMMERCE_PROVIDER=spree
|
||||||
|
|
||||||
{# public (available in the web browser) #}
|
{# - public (available in the web browser) #}
|
||||||
NEXT_PUBLIC_SPREE_API_HOST=http://localhost:3000
|
NEXT_PUBLIC_SPREE_API_HOST=http://localhost:3000
|
||||||
NEXT_PUBLIC_SPREE_DEFAULT_LOCALE=en-us
|
NEXT_PUBLIC_SPREE_DEFAULT_LOCALE=en-us
|
||||||
NEXT_PUBLIC_SPREE_CART_COOKIE_NAME=spree_cart
|
NEXT_PUBLIC_SPREE_CART_COOKIE_NAME=spree_cart_token
|
||||||
|
{# -- cookie expire in days #}
|
||||||
|
NEXT_PUBLIC_SPREE_CART_COOKIE_EXPIRE=7
|
||||||
NEXT_PUBLIC_SPREE_IMAGE_HOST=http://localhost:3000
|
NEXT_PUBLIC_SPREE_IMAGE_HOST=http://localhost:3000
|
||||||
NEXT_PUBLIC_SPREE_ALLOWED_IMAGE_DOMAIN=localhost
|
NEXT_PUBLIC_SPREE_ALLOWED_IMAGE_DOMAIN=localhost
|
||||||
NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_ID=1
|
NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_ID=1
|
||||||
NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_ID=27
|
NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_ID=27
|
||||||
NEXT_PUBLIC_SHOW_SINGLE_VARIANT_OPTIONS=false
|
NEXT_PUBLIC_SPREE_SHOW_SINGLE_VARIANT_OPTIONS=false
|
||||||
|
@ -1,17 +1,75 @@
|
|||||||
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
import useAddItem from '@commerce/cart/use-add-item'
|
||||||
import { MutationHook } from '@commerce/utils/types'
|
import type { UseAddItem } from '@commerce/cart/use-add-item'
|
||||||
|
import type { MutationHook } from '@commerce/utils/types'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
import useCart from './use-cart'
|
||||||
|
import type { AddItemHook } from '@commerce/types/cart'
|
||||||
|
import normalizeCart from '@framework/utils/normalizeCart'
|
||||||
|
import type { GraphQLFetcherResult } from '@commerce/api'
|
||||||
|
import type { IOrder } from '@spree/storefront-api-v2-sdk/types/interfaces/Order'
|
||||||
|
import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
|
||||||
|
import type { AddItem } from '@spree/storefront-api-v2-sdk/types/interfaces/endpoints/CartClass'
|
||||||
|
import getCartToken from '@framework/utils/getCartToken'
|
||||||
|
|
||||||
export default useAddItem as UseAddItem<typeof handler>
|
export default useAddItem as UseAddItem<typeof handler>
|
||||||
export const handler: MutationHook<any> = {
|
|
||||||
|
export const handler: MutationHook<AddItemHook> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
|
url: '__UNUSED__',
|
||||||
query: '',
|
query: '',
|
||||||
},
|
},
|
||||||
async fetcher({ input, options, fetch }) {},
|
async fetcher({ input, options, fetch }) {
|
||||||
|
console.info(
|
||||||
|
'useAddItem fetcher called. Configuration: ',
|
||||||
|
'input: ',
|
||||||
|
input,
|
||||||
|
'options: ',
|
||||||
|
options
|
||||||
|
)
|
||||||
|
|
||||||
|
const { quantity, productId, variantId } = input
|
||||||
|
|
||||||
|
const safeQuantity = quantity ?? 1
|
||||||
|
|
||||||
|
const token: IToken = { orderToken: getCartToken() }
|
||||||
|
const addItemParameters: AddItem = {
|
||||||
|
variant_id: variantId,
|
||||||
|
quantity: safeQuantity,
|
||||||
|
include: [
|
||||||
|
'line_items',
|
||||||
|
'line_items.variant',
|
||||||
|
'line_items.variant.product',
|
||||||
|
'line_items.variant.product.images',
|
||||||
|
'line_items.variant.images',
|
||||||
|
'line_items.variant.option_values',
|
||||||
|
'line_items.variant.product.option_types',
|
||||||
|
].join(','),
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { data: spreeSuccessResponse },
|
||||||
|
} = await fetch<GraphQLFetcherResult<{ data: IOrder }>>({
|
||||||
|
variables: {
|
||||||
|
methodPath: 'cart.addItem',
|
||||||
|
arguments: [token, addItemParameters],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return normalizeCart(spreeSuccessResponse, spreeSuccessResponse.data)
|
||||||
|
},
|
||||||
useHook:
|
useHook:
|
||||||
({ fetch }) =>
|
({ fetch }) =>
|
||||||
() => {
|
() => {
|
||||||
return async function addItem() {
|
console.log('useAddItem useHook called.')
|
||||||
return {}
|
|
||||||
}
|
const { mutate, data: cartData } = useCart()
|
||||||
|
|
||||||
|
return useCallback(async (input) => {
|
||||||
|
const data = await fetch({ input })
|
||||||
|
|
||||||
|
await mutate(data, false)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}, [])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,100 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { SWRHook } from '@commerce/utils/types'
|
import type { SWRHook } from '@commerce/utils/types'
|
||||||
import useCart, { UseCart } from '@commerce/cart/use-cart'
|
import useCart from '@commerce/cart/use-cart'
|
||||||
|
import type { UseCart } from '@commerce/cart/use-cart'
|
||||||
|
import type { GetCartHook } from '@commerce/types/cart'
|
||||||
|
import normalizeCart from '@framework/utils/normalizeCart'
|
||||||
|
import type { GraphQLFetcherResult } from '@commerce/api'
|
||||||
|
import type { IOrder } from '@spree/storefront-api-v2-sdk/types/interfaces/Order'
|
||||||
|
import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
|
||||||
|
import setCartToken from '@framework/utils/setCartToken'
|
||||||
|
|
||||||
export default useCart as UseCart<typeof handler>
|
export default useCart as UseCart<typeof handler>
|
||||||
|
|
||||||
export const handler: SWRHook<any> = {
|
// This handler avoids calling /api/cart.
|
||||||
|
// There doesn't seem to be a good reason to call it.
|
||||||
|
// So far, only @framework/bigcommerce uses it.
|
||||||
|
export const handler: SWRHook<GetCartHook> = {
|
||||||
fetchOptions: {
|
fetchOptions: {
|
||||||
|
url: '__UNUSED__',
|
||||||
query: '',
|
query: '',
|
||||||
},
|
},
|
||||||
async fetcher() {
|
async fetcher({ input, options, fetch }) {
|
||||||
return {
|
console.info(
|
||||||
id: '',
|
'useCart fetcher called. Configuration: ',
|
||||||
createdAt: '',
|
'input: ',
|
||||||
currency: { code: '' },
|
input,
|
||||||
taxesIncluded: '',
|
'options: ',
|
||||||
lineItems: [],
|
options
|
||||||
lineItemsSubtotalPrice: '',
|
)
|
||||||
subtotalPrice: 0,
|
|
||||||
totalPrice: 0,
|
const { cartId: cartToken } = input
|
||||||
|
let spreeCartResponse: IOrder | null
|
||||||
|
|
||||||
|
if (!cartToken) {
|
||||||
|
spreeCartResponse = null
|
||||||
|
} else {
|
||||||
|
const spreeToken: IToken = { orderToken: cartToken }
|
||||||
|
const {
|
||||||
|
data: { data: spreeCartShowSuccessResponse },
|
||||||
|
} = await fetch<GraphQLFetcherResult<{ data: IOrder }>>({
|
||||||
|
variables: {
|
||||||
|
methodPath: 'cart.show',
|
||||||
|
arguments: [
|
||||||
|
spreeToken,
|
||||||
|
{
|
||||||
|
include: [
|
||||||
|
'line_items',
|
||||||
|
'line_items.variant',
|
||||||
|
'line_items.variant.product',
|
||||||
|
'line_items.variant.product.images',
|
||||||
|
'line_items.variant.images',
|
||||||
|
'line_items.variant.option_values',
|
||||||
|
'line_items.variant.product.option_types',
|
||||||
|
].join(','),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
spreeCartResponse = spreeCartShowSuccessResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!spreeCartResponse || spreeCartResponse?.data.attributes.completed_at) {
|
||||||
|
const {
|
||||||
|
data: { data: spreeCartCreateSuccessResponse },
|
||||||
|
} = await fetch<GraphQLFetcherResult<{ data: IOrder }>>({
|
||||||
|
variables: {
|
||||||
|
methodPath: 'cart.create',
|
||||||
|
arguments: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
setCartToken(spreeCartCreateSuccessResponse.data.attributes.token)
|
||||||
|
|
||||||
|
spreeCartResponse = spreeCartCreateSuccessResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizeCart(spreeCartResponse, spreeCartResponse.data)
|
||||||
},
|
},
|
||||||
useHook:
|
useHook:
|
||||||
({ useData }) =>
|
({ useData }) =>
|
||||||
(input) => {
|
(input = {}) => {
|
||||||
return useMemo(
|
console.log('useCart useHook called.')
|
||||||
() =>
|
|
||||||
Object.create(
|
const response = useData({
|
||||||
{},
|
swrOptions: { revalidateOnFocus: false, ...input.swrOptions },
|
||||||
{
|
})
|
||||||
isEmpty: {
|
|
||||||
get() {
|
return useMemo<typeof response & { isEmpty: boolean }>(() => {
|
||||||
return true
|
return Object.create(response, {
|
||||||
},
|
isEmpty: {
|
||||||
enumerable: true,
|
get() {
|
||||||
},
|
return (response.data?.lineItems.length ?? 0) === 0
|
||||||
}
|
},
|
||||||
),
|
enumerable: true,
|
||||||
[]
|
},
|
||||||
)
|
})
|
||||||
|
}, [response])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
"provider": "spree",
|
"provider": "spree",
|
||||||
"features": {
|
"features": {
|
||||||
"wishlist": false,
|
"wishlist": false,
|
||||||
"cart": false,
|
"cart": true,
|
||||||
"search": false,
|
"search": true,
|
||||||
"customerAuth": false
|
"customerAuth": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
framework/spree/errors/MissingLineItemVariantError.ts
Normal file
1
framework/spree/errors/MissingLineItemVariantError.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default class MissingLineItemVariantError extends Error {}
|
@ -1,16 +1,20 @@
|
|||||||
import forceIsomorphicConfigValues from './utils/forceIsomorphicConfigValues'
|
import forceIsomorphicConfigValues from './utils/forceIsomorphicConfigValues'
|
||||||
import requireConfig from './utils/requireConfig'
|
import requireConfig from './utils/requireConfig'
|
||||||
|
import validateCookieExpire from './utils/validateCookieExpire'
|
||||||
|
|
||||||
const isomorphicConfig = {
|
const isomorphicConfig = {
|
||||||
spreeApiHost: process.env.NEXT_PUBLIC_SPREE_API_HOST,
|
spreeApiHost: process.env.NEXT_PUBLIC_SPREE_API_HOST,
|
||||||
defaultLocale: process.env.NEXT_PUBLIC_SPREE_DEFAULT_LOCALE,
|
defaultLocale: process.env.NEXT_PUBLIC_SPREE_DEFAULT_LOCALE,
|
||||||
cartCookieName: process.env.NEXT_PUBLIC_SPREE_CART_COOKIE_NAME,
|
cartCookieName: process.env.NEXT_PUBLIC_SPREE_CART_COOKIE_NAME,
|
||||||
|
cartCookieExpire: validateCookieExpire(
|
||||||
|
process.env.NEXT_PUBLIC_SPREE_CART_COOKIE_EXPIRE
|
||||||
|
),
|
||||||
spreeImageHost: process.env.NEXT_PUBLIC_SPREE_IMAGE_HOST,
|
spreeImageHost: process.env.NEXT_PUBLIC_SPREE_IMAGE_HOST,
|
||||||
spreeCategoriesTaxonomyId:
|
spreeCategoriesTaxonomyId:
|
||||||
process.env.NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_ID,
|
process.env.NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_ID,
|
||||||
spreeBrandsTaxonomyId: process.env.NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_ID,
|
spreeBrandsTaxonomyId: process.env.NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_ID,
|
||||||
showSingleVariantOptions:
|
showSingleVariantOptions:
|
||||||
process.env.NEXT_PUBLIC_SHOW_SINGLE_VARIANT_OPTIONS === 'true',
|
process.env.NEXT_PUBLIC_SPREE_SHOW_SINGLE_VARIANT_OPTIONS === 'true',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default forceIsomorphicConfigValues(
|
export default forceIsomorphicConfigValues(
|
||||||
@ -20,6 +24,7 @@ export default forceIsomorphicConfigValues(
|
|||||||
'spreeApiHost',
|
'spreeApiHost',
|
||||||
'defaultLocale',
|
'defaultLocale',
|
||||||
'cartCookieName',
|
'cartCookieName',
|
||||||
|
'cartCookieExpire',
|
||||||
'spreeImageHost',
|
'spreeImageHost',
|
||||||
'spreeCategoriesTaxonomyId',
|
'spreeCategoriesTaxonomyId',
|
||||||
'spreeBrandsTaxonomyId',
|
'spreeBrandsTaxonomyId',
|
||||||
|
@ -10,8 +10,9 @@ import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces
|
|||||||
import SpreeResponseContentError from '../errors/SpreeResponseContentError'
|
import SpreeResponseContentError from '../errors/SpreeResponseContentError'
|
||||||
import { findIncluded } from './jsonApi'
|
import { findIncluded } from './jsonApi'
|
||||||
|
|
||||||
const isColorProductOption = (productOption: ProductOption) =>
|
const isColorProductOption = (productOption: ProductOption) => {
|
||||||
productOption.displayName === 'Color'
|
return productOption.displayName === 'Color'
|
||||||
|
}
|
||||||
|
|
||||||
const expandOptions = (
|
const expandOptions = (
|
||||||
spreeSuccessResponse: JsonApiResponse,
|
spreeSuccessResponse: JsonApiResponse,
|
||||||
|
7
framework/spree/utils/getCartToken.ts
Normal file
7
framework/spree/utils/getCartToken.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { requireConfigValue } from '@framework/isomorphicConfig'
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
const getCartToken = () =>
|
||||||
|
Cookies.get(requireConfigValue('cartCookieName') as string)
|
||||||
|
|
||||||
|
export default getCartToken
|
202
framework/spree/utils/normalizeCart.ts
Normal file
202
framework/spree/utils/normalizeCart.ts
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import type {
|
||||||
|
Cart,
|
||||||
|
LineItem,
|
||||||
|
ProductVariant,
|
||||||
|
SelectedOption,
|
||||||
|
} from '@commerce/types/cart'
|
||||||
|
import MissingLineItemVariantError from '@framework/errors/MissingLineItemVariantError'
|
||||||
|
import { requireConfigValue } from '@framework/isomorphicConfig'
|
||||||
|
import type {
|
||||||
|
JsonApiDocument,
|
||||||
|
JsonApiListResponse,
|
||||||
|
JsonApiSingleResponse,
|
||||||
|
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
|
||||||
|
import type { OrderAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Order'
|
||||||
|
import { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
|
||||||
|
import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
|
||||||
|
import createGetAbsoluteImageUrl from './createGetAbsoluteImageUrl'
|
||||||
|
import getMediaGallery from './getMediaGallery'
|
||||||
|
import { findIncluded, findIncludedOfType } from './jsonApi'
|
||||||
|
|
||||||
|
const isColorProductOption = (productOptionType: any) => {
|
||||||
|
// TODO: Fix type and merge with isColorProductOption in framework/spree/utils/expandOptions.ts
|
||||||
|
return productOptionType.attributes.presentation === 'Color'
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeVariant = (
|
||||||
|
spreeSuccessResponse: JsonApiSingleResponse | JsonApiListResponse,
|
||||||
|
spreeVariant: JsonApiDocument
|
||||||
|
): ProductVariant => {
|
||||||
|
const productIdentifier = spreeVariant.relationships.product
|
||||||
|
.data as RelationType
|
||||||
|
const spreeProduct = findIncluded<ProductAttr>(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
productIdentifier.type,
|
||||||
|
productIdentifier.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (spreeProduct === null) {
|
||||||
|
throw new MissingLineItemVariantError(
|
||||||
|
`Couldn't find product with id ${productIdentifier.id}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const spreeVariantImageRecords = findIncludedOfType(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
spreeVariant,
|
||||||
|
'images'
|
||||||
|
)
|
||||||
|
|
||||||
|
let lineItemImage
|
||||||
|
|
||||||
|
const variantImage = getMediaGallery(
|
||||||
|
spreeVariantImageRecords,
|
||||||
|
createGetAbsoluteImageUrl(requireConfigValue('spreeImageHost') as string)
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
if (variantImage) {
|
||||||
|
lineItemImage = variantImage
|
||||||
|
} else {
|
||||||
|
const spreeProductImageRecords = findIncludedOfType(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
spreeProduct,
|
||||||
|
'images'
|
||||||
|
)
|
||||||
|
|
||||||
|
const productImage = getMediaGallery(
|
||||||
|
spreeProductImageRecords,
|
||||||
|
createGetAbsoluteImageUrl(requireConfigValue('spreeImageHost') as string)
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
lineItemImage = productImage
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: spreeVariant.id,
|
||||||
|
sku: spreeVariant.attributes.sku,
|
||||||
|
name: spreeProduct.attributes.name,
|
||||||
|
requiresShipping: true,
|
||||||
|
price: parseFloat(spreeVariant.attributes.price),
|
||||||
|
listPrice: parseFloat(spreeVariant.attributes.price),
|
||||||
|
image: lineItemImage,
|
||||||
|
isInStock: spreeVariant.attributes.in_stock,
|
||||||
|
availableForSale: spreeVariant.attributes.purchasable,
|
||||||
|
weight: spreeVariant.attributes.weight,
|
||||||
|
height: spreeVariant.attributes.height,
|
||||||
|
width: spreeVariant.attributes.width,
|
||||||
|
depth: spreeVariant.attributes.depth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeLineItem = (
|
||||||
|
spreeSuccessResponse: JsonApiSingleResponse | JsonApiListResponse,
|
||||||
|
spreeLineItem: JsonApiDocument
|
||||||
|
): LineItem => {
|
||||||
|
//TODO: Replace JsonApiDocument type in spreeLineItem with more specific, new Spree line item item
|
||||||
|
const variantIdentifier = spreeLineItem.relationships.variant
|
||||||
|
.data as RelationType
|
||||||
|
const variant = findIncluded(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
variantIdentifier.type,
|
||||||
|
variantIdentifier.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (variant === null) {
|
||||||
|
throw new MissingLineItemVariantError(
|
||||||
|
`Couldn't find variant with id ${variantIdentifier.id}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const productIdentifier = variant.relationships.product.data as RelationType
|
||||||
|
const product = findIncluded<ProductAttr>(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
productIdentifier.type,
|
||||||
|
productIdentifier.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (product === null) {
|
||||||
|
throw new MissingLineItemVariantError(
|
||||||
|
`Couldn't find product with id ${productIdentifier.id}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = `/${product.attributes.slug}`
|
||||||
|
|
||||||
|
const spreeOptionValues = findIncludedOfType(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
variant,
|
||||||
|
'option_values'
|
||||||
|
)
|
||||||
|
|
||||||
|
const options: SelectedOption[] = spreeOptionValues.map(
|
||||||
|
(spreeOptionValue) => {
|
||||||
|
const spreeOptionTypeIdentifier = spreeOptionValue.relationships
|
||||||
|
.option_type.data as RelationType
|
||||||
|
|
||||||
|
const spreeOptionType = findIncluded(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
spreeOptionTypeIdentifier.type,
|
||||||
|
spreeOptionTypeIdentifier.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (spreeOptionType === null) {
|
||||||
|
throw new MissingLineItemVariantError(
|
||||||
|
`Couldn't find option type with id ${spreeOptionTypeIdentifier.id}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = isColorProductOption(spreeOptionType)
|
||||||
|
? spreeOptionValue.attributes.name
|
||||||
|
: spreeOptionValue.attributes.presentation
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: spreeOptionValue.id,
|
||||||
|
name: spreeOptionType.attributes.presentation,
|
||||||
|
value: label,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: spreeLineItem.id,
|
||||||
|
variantId: variant.id,
|
||||||
|
productId: productIdentifier.id,
|
||||||
|
name: spreeLineItem.attributes.name,
|
||||||
|
quantity: spreeLineItem.attributes.quantity,
|
||||||
|
discounts: [], // TODO: Retrieve from Spree
|
||||||
|
path,
|
||||||
|
variant: normalizeVariant(spreeSuccessResponse, variant),
|
||||||
|
options,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizeCart = (
|
||||||
|
spreeSuccessResponse: JsonApiSingleResponse | JsonApiListResponse,
|
||||||
|
spreeCart: OrderAttr
|
||||||
|
): Cart => {
|
||||||
|
const lineItems = findIncludedOfType(
|
||||||
|
spreeSuccessResponse,
|
||||||
|
spreeCart,
|
||||||
|
'line_items'
|
||||||
|
).map((lineItem) => normalizeLineItem(spreeSuccessResponse, lineItem))
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: spreeCart.id,
|
||||||
|
createdAt: spreeCart.attributes.created_at.toString(),
|
||||||
|
currency: { code: spreeCart.attributes.currency },
|
||||||
|
taxesIncluded: true,
|
||||||
|
lineItems,
|
||||||
|
lineItemsSubtotalPrice: parseFloat(spreeCart.attributes.item_total),
|
||||||
|
// TODO: We need a value from Spree which includes item total and discounts in one value for subtotalPrice.
|
||||||
|
subtotalPrice: parseFloat(spreeCart.attributes.item_total),
|
||||||
|
totalPrice: parseFloat(spreeCart.attributes.total),
|
||||||
|
customerId: spreeCart.attributes.token,
|
||||||
|
email: spreeCart.attributes.email,
|
||||||
|
discounts: [],
|
||||||
|
// discounts: [{value: number}] // TODO: Retrieve from Spree
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { normalizeLineItem }
|
||||||
|
|
||||||
|
export default normalizeCart
|
@ -1,4 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
|
Product,
|
||||||
ProductOption,
|
ProductOption,
|
||||||
ProductPrice,
|
ProductPrice,
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
@ -18,7 +19,7 @@ import { findIncludedOfType } from './jsonApi'
|
|||||||
const normalizeProduct = (
|
const normalizeProduct = (
|
||||||
spreeSuccessResponse: JsonApiSingleResponse | JsonApiListResponse,
|
spreeSuccessResponse: JsonApiSingleResponse | JsonApiListResponse,
|
||||||
spreeProduct: ProductAttr
|
spreeProduct: ProductAttr
|
||||||
) => {
|
): Product => {
|
||||||
const spreeImageRecords = findIncludedOfType(
|
const spreeImageRecords = findIncludedOfType(
|
||||||
spreeSuccessResponse,
|
spreeSuccessResponse,
|
||||||
spreeProduct,
|
spreeProduct,
|
||||||
|
16
framework/spree/utils/setCartToken.ts
Normal file
16
framework/spree/utils/setCartToken.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { requireConfigValue } from '@framework/isomorphicConfig'
|
||||||
|
import Cookies from 'js-cookie'
|
||||||
|
|
||||||
|
const setCartToken = (cartToken: string) => {
|
||||||
|
const cookieOptions = {
|
||||||
|
expires: requireConfigValue('cartCookieExpire') as number,
|
||||||
|
}
|
||||||
|
|
||||||
|
Cookies.set(
|
||||||
|
requireConfigValue('cartCookieName') as string,
|
||||||
|
cartToken,
|
||||||
|
cookieOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default setCartToken
|
21
framework/spree/utils/validateCookieExpire.ts
Normal file
21
framework/spree/utils/validateCookieExpire.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const validateCookieExpire = (expire: unknown) => {
|
||||||
|
let expireInteger: number
|
||||||
|
|
||||||
|
if (typeof expire === 'string') {
|
||||||
|
expireInteger = parseFloat(expire || '')
|
||||||
|
} else if (typeof expire === 'number') {
|
||||||
|
expireInteger = expire
|
||||||
|
} else {
|
||||||
|
throw new TypeError(
|
||||||
|
'expire must be a string containing a number or an integer.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expireInteger < 0) {
|
||||||
|
throw new RangeError('expire must be non-negative.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return expireInteger
|
||||||
|
}
|
||||||
|
|
||||||
|
export default validateCookieExpire
|
Loading…
x
Reference in New Issue
Block a user