diff --git a/.gitignore b/.gitignore index d99d128c98e..ac3d3a3192c 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ bower_components/ node_modules/ storybook-static/ .nx/ + +.qodo diff --git a/platform/canvas-packages/internal_pkgs/spree/package.json b/platform/canvas-packages/internal_pkgs/spree/package.json index f2e1d2c7d33..f7e143147b3 100644 --- a/platform/canvas-packages/internal_pkgs/spree/package.json +++ b/platform/canvas-packages/internal_pkgs/spree/package.json @@ -1,6 +1,6 @@ { "name": "commerce-spree", - "version": "0.0.30", + "version": "0.1.0", "description": "Plasmic registration calls for spree commerce provider", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -38,6 +38,7 @@ "@types/node": "^17.0.8", "@types/react": "^18.0.27", "next": "^12.0.8", + "patch-package": "^8.0.0", "prettier": "^2.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/platform/canvas-packages/internal_pkgs/spree/patches/@plasmicpkgs+commerce+0.0.205.patch b/platform/canvas-packages/internal_pkgs/spree/patches/@plasmicpkgs+commerce+0.0.205.patch new file mode 100644 index 00000000000..f71576efbc6 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/patches/@plasmicpkgs+commerce+0.0.205.patch @@ -0,0 +1,40 @@ +diff --git a/node_modules/@plasmicpkgs/commerce/dist/commerce.d.ts b/node_modules/@plasmicpkgs/commerce/dist/commerce.d.ts +index a23a631..e4075b1 100644 +--- a/node_modules/@plasmicpkgs/commerce/dist/commerce.d.ts ++++ b/node_modules/@plasmicpkgs/commerce/dist/commerce.d.ts +@@ -18,6 +18,35 @@ export declare type Provider = CommerceConfig & { + useBrands?: SWRHook; + }; + extraFeatures?: CommerceExtraFeatures; ++ checkout?: { ++ useCheckout?: SWRHook ++ useSubmitCheckout?: MutationHook ++ } ++ wishlist?: { ++ useWishlist?: SWRHook ++ useAddItem?: MutationHook ++ useRemoveItem?: MutationHook ++ } ++ customer?: { ++ useCustomer?: SWRHook ++ card?: { ++ useCards?: SWRHook ++ useAddItem?: MutationHook ++ useUpdateItem?: MutationHook ++ useRemoveItem?: MutationHook ++ } ++ address?: { ++ useAddresses?: SWRHook ++ useAddItem?: MutationHook ++ useUpdateItem?: MutationHook ++ useRemoveItem?: MutationHook ++ } ++ } ++ auth?: { ++ useSignup?: MutationHook ++ useLogin?: MutationHook ++ useLogout?: MutationHook ++ } + }; + export declare type CommerceConfig = { + locale: string; diff --git a/platform/canvas-packages/internal_pkgs/spree/src/cart/use-cart.tsx b/platform/canvas-packages/internal_pkgs/spree/src/cart/use-cart.tsx index faf6ba8e38f..f9d10318af8 100644 --- a/platform/canvas-packages/internal_pkgs/spree/src/cart/use-cart.tsx +++ b/platform/canvas-packages/internal_pkgs/spree/src/cart/use-cart.tsx @@ -3,20 +3,9 @@ import type { SWRHook } from '@plasmicpkgs/commerce' import { useCart as useCommerceCart, UseCart } from '@plasmicpkgs/commerce' import type { GetCartHook } from '../types/cart' import normalizeCart from '../utils/normalizations/normalize-cart' -import type { GraphQLFetcherResult } from '../types' -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 { FetcherError } from '@plasmicpkgs/commerce' -import { setCartToken } from '../utils/tokens/cart-token' -import ensureIToken from '../utils/tokens/ensure-itoken' -import isLoggedIn from '../utils/tokens/is-logged-in' -import createEmptyCart from '../utils/create-empty-cart' -import { requireConfigValue } from '../isomorphic-config' +import getCart from '../utils/get-cart' -const imagesSize = requireConfigValue('imagesSize') as string -const imagesQuality = requireConfigValue('imagesQuality') as number - -export default useCommerceCart as UseCart; +export default useCommerceCart as UseCart // This handler avoids calling /api/cart. // There doesn't seem to be a good reason to call it. @@ -36,64 +25,7 @@ export const handler: SWRHook = { options ) - let spreeCartResponse: IOrder | null - - const token: IToken | undefined = ensureIToken() - - if (!token) { - spreeCartResponse = null - } else { - try { - const { data: spreeCartShowSuccessResponse } = await fetch< - GraphQLFetcherResult - >({ - variables: { - methodPath: 'cart.show', - arguments: [ - token, - { - 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(','), - image_transformation: { - quality: imagesQuality, - size: imagesSize, - }, - }, - ], - }, - }) - - spreeCartResponse = spreeCartShowSuccessResponse - } catch (fetchCartError) { - if ( - !(fetchCartError instanceof FetcherError) || - fetchCartError.status !== 404 - ) { - throw fetchCartError - } - - spreeCartResponse = null - } - } - - if (!spreeCartResponse || spreeCartResponse?.data.attributes.completed_at) { - const { data: spreeCartCreateSuccessResponse } = await createEmptyCart( - fetch - ) - - spreeCartResponse = spreeCartCreateSuccessResponse - - if (!isLoggedIn()) { - setCartToken(spreeCartResponse.data.attributes.token) - } - } + const spreeCartResponse = await getCart(fetch) return normalizeCart(spreeCartResponse, spreeCartResponse.data) }, diff --git a/platform/canvas-packages/internal_pkgs/spree/src/checkout/use-checkout.tsx b/platform/canvas-packages/internal_pkgs/spree/src/checkout/use-checkout.tsx new file mode 100644 index 00000000000..b8a605440a1 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/checkout/use-checkout.tsx @@ -0,0 +1,60 @@ +import { SWRHook } from '@plasmicpkgs/commerce' +import useCheckout, { UseCheckout } from '../commerce/checkout/use-checkout' +import getCart from '../utils/get-cart' +import normalizeCart from '../utils/normalizations/normalize-cart' +import { useMemo } from 'react' +import { GetCheckoutHook } from '../commerce/types/checkout' + +export default useCheckout as UseCheckout + +export const handler: SWRHook = { + // Provide fetchOptions for SWR cache key + fetchOptions: { + // TODO: Revise url and query + url: 'checkout', + query: 'show', + }, + async fetcher({ input, options, fetch }) { + console.info( + 'useCheckout fetcher called. Configuration: ', + 'input: ', + input, + 'options: ', + options + ) + const spreeCartResponse = await getCart(fetch) + const cart = normalizeCart(spreeCartResponse, spreeCartResponse.data) + return { + hasPayment: false, + hasShipping: false, + addressId: null, + payments: [], + cardId: null, + lineItems: cart.lineItems, + } + }, + useHook: ({ useData }) => { + const useWrappedHook: ReturnType['useHook']> = ( + input + ) => { + const response = useData({ + swrOptions: { revalidateOnFocus: false, ...input?.swrOptions }, + }) + + return useMemo( + () => + Object.create(response, { + isEmpty: { + get() { + return response.data?.lineItems?.length ?? 0 + }, + enumerable: true, + }, + }), + [response] + ) + } + + return useWrappedHook + }, +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/checkout/use-submit-checkout.tsx b/platform/canvas-packages/internal_pkgs/spree/src/checkout/use-submit-checkout.tsx new file mode 100644 index 00000000000..4f1009ad89c --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/checkout/use-submit-checkout.tsx @@ -0,0 +1,80 @@ +import type { SubmitCheckoutHook } from '../commerce/types/checkout' +import type { MutationHook } from '../commerce/utils/types' + +import { useCallback } from 'react' +import useSubmitCheckout, { + UseSubmitCheckout, +} from '../commerce/checkout/use-submit-checkout' +import { ValidationError } from '@plasmicpkgs/commerce' +import submitCheckout from '../utils/submit-checkout' +import useCheckout from './use-checkout' + +export default useSubmitCheckout as UseSubmitCheckout + +export const handler: MutationHook = { + // Provide fetchOptions for SWR cache key + fetchOptions: { + url: 'checkout', + query: 'orderUpdate', + }, + async fetcher({ input, options, fetch }) { + console.info( + 'useSubmitCheckout fetcher called. Configuration: ', + 'input: ', + input, + 'options: ', + options + ) + return await submitCheckout(fetch, input) + }, + + useHook: ({ fetch }) => { + const useWrappedHook: ReturnType< + MutationHook['useHook'] + > = () => { + const { mutate } = useCheckout() + + return useCallback( + async (input) => { + const { + email, + special_instructions, + billing_address, + shipping_address, + payments, + onSuccessAction, + } = input + + if ( + !email && + !special_instructions && + !billing_address && + !shipping_address && + !payments + ) { + throw new ValidationError({ + message: + 'email or special_instructions or billing_address or shipping_address or payments needs to be provided.', + }) + } + const data = await fetch({ + input: { + email, + special_instructions, + billing_address, + shipping_address, + payments, + onSuccessAction, + }, + }) + + await mutate(data, false) + + return data + }, + [mutate] + ) + } + return useWrappedHook + }, +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/checkout/use-checkout.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/checkout/use-checkout.ts new file mode 100644 index 00000000000..110add9b11a --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/checkout/use-checkout.ts @@ -0,0 +1,34 @@ +import type { SWRHook, HookFetcherFn } from '@plasmicpkgs/commerce' +import type { GetCheckoutHook } from '../types/checkout' + +import Cookies from 'js-cookie' + +import { useHook, useSWRHook } from '../utils/use-hook' +import { Provider, useCommerce } from '@plasmicpkgs/commerce' + +export type UseCheckout< + H extends SWRHook = SWRHook +> = ReturnType + +export const fetcher: HookFetcherFn = async ({ + options, + input: { cartId }, + fetch, +}) => { + return cartId ? await fetch(options) : null +} + +const fn = (provider: Provider) => provider.checkout?.useCheckout! + +const useCheckout: UseCheckout = (input) => { + const hook = useHook(fn) + const { cartCookie } = useCommerce() + const fetcherFn = hook.fetcher ?? fetcher + const wrapper: typeof fetcher = (context) => { + context.input.cartId = Cookies.get(cartCookie) + return fetcherFn(context) + } + return useSWRHook({ ...hook, fetcher: wrapper })(input) +} + +export default useCheckout diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/checkout/use-submit-checkout.tsx b/platform/canvas-packages/internal_pkgs/spree/src/commerce/checkout/use-submit-checkout.tsx new file mode 100644 index 00000000000..18919a87fd1 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/checkout/use-submit-checkout.tsx @@ -0,0 +1,23 @@ +import type { HookFetcherFn, MutationHook } from '../utils/types' +import type { SubmitCheckoutHook } from '../types/checkout' +import type { Provider } from '@plasmicpkgs/commerce' + +import { useHook, useMutationHook } from '../utils/use-hook' +import { mutationFetcher } from '../utils/default-fetcher' + +export type UseSubmitCheckout< + H extends MutationHook< + SubmitCheckoutHook + > = MutationHook +> = ReturnType + +export const fetcher: HookFetcherFn = mutationFetcher + +const fn = (provider: Provider) => provider.checkout?.useSubmitCheckout! + +const useSubmitCheckout: UseSubmitCheckout = (...args) => { + const hook = useHook(fn) + return useMutationHook({ fetcher, ...hook })(...args) +} + +export default useSubmitCheckout diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/index.tsx b/platform/canvas-packages/internal_pkgs/spree/src/commerce/index.tsx new file mode 100644 index 00000000000..e02242360cb --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/index.tsx @@ -0,0 +1,74 @@ +/* + Forked from https://github.com/vercel/commerce/tree/main/packages/commerce/src + Changes: Removed authentication, customer and wishlist hooks +*/ +import React, { + ReactNode, + MutableRefObject, + createContext, + useContext, + useMemo, + useRef, +} from 'react' +import type { Fetcher } from './utils/types' +import { Provider } from '@plasmicpkgs/commerce' + +const Commerce = createContext | {}>({}) + +export type CommerceConfig = { + locale: string + cartCookie: string +} + +export type CommerceContextValue

= { + providerRef: MutableRefObject

+ fetcherRef: MutableRefObject +} & CommerceConfig + +export type CommerceProps

= { + children?: ReactNode + provider: P +} + +/** + * These are the properties every provider should allow when implementing + * the core commerce provider + */ +export type CommerceProviderProps = { + children?: ReactNode +} & Partial + +export function CoreCommerceProvider

({ + provider, + children, +}: CommerceProps

) { + const providerRef = useRef(provider) + // TODO: Remove the fetcherRef + const fetcherRef = useRef(provider.fetcher) + // If the parent re-renders this provider will re-render every + // consumer unless we memoize the config + const { locale, cartCookie } = providerRef.current + const cfg = useMemo( + () => ({ providerRef, fetcherRef, locale, cartCookie }), + [locale, cartCookie] + ) + + return {children} +} + +export function getCommerceProvider

(provider: P) { + return function CommerceProvider({ + children, + ...props + }: CommerceProviderProps) { + return ( + + {children} + + ) + } +} + +export function useCommerce

() { + return useContext(Commerce) as CommerceContextValue

+} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/cart.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/cart.ts new file mode 100644 index 00000000000..014fed87335 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/cart.ts @@ -0,0 +1,265 @@ +import type { Discount, Image, Measurement } from './common' + +// TODO: This should use the same type as the `ProductVariant` type from `product.ts` +export interface ProductVariant { + /** + * The unique identifier for the variant. + */ + id: string + /** + * The SKU (stock keeping unit) associated with the product variant. + */ + sku?: string + /** + * The product variant’s name, or the product's name. + */ + name: string + /** + * The product variant’s price after all discounts are applied. + */ + price: number + /** + * The product variant’s price before discounts are applied. + */ + listPrice: number + /** + * Indicates if the variant is available for sale. + */ + availableForSale?: boolean + /** + * Whether a customer needs to provide a shipping address when placing + * an order for the product variant. + */ + requiresShipping?: boolean + /** + * The image associated with the variant. + */ + image?: Image + /** + * The variant's weight. If a weight was not explicitly specified on the + * variant, this will be the product's weight. + */ + weight?: Measurement + /** + * The variant's height. If a height was not explicitly specified on the + * variant, this will be the product's height. + */ + height?: Measurement + /** + * The variant's width. If a width was not explicitly specified on the + * variant, this will be the product's width. + */ + width?: Measurement + /** + * The variant's depth. If a depth was not explicitly specified on the + * variant, this will be the product's depth. + */ + depth?: Measurement +} + +export interface SelectedOption { + /** + * The unique identifier for the option. + */ + id?: string + /** + * The product option’s name, such as "Color" or "Size". + */ + name: string + /** + * The product option’s value, such as "Red" or "XL". + */ + value: string +} + +export interface LineItem { + /** + * The unique identifier for the line item. + */ + id: string + /** + * The unique identifier for the product variant. + */ + variantId: string + /** + * The unique identifier for the product, if the variant is not provided. + */ + productId: string + /** + * This is usually the product's name. + */ + name: string + /** + * The quantity of the product variant in the line item. + */ + quantity: number + /** + * List of discounts applied to the line item. + */ + discounts: Discount[] + /** + * A human-friendly unique string automatically generated from the product’s name. + */ + path: string + /** + * The product variant. + */ + variant: ProductVariant + /** + * List of selected options, to be used when displaying the line item, such as Color: Red, Size: XL. + */ + options?: SelectedOption[] +} + +/** + * Shopping cart, a.k.a Checkout + */ +export interface Cart { + /** + * The unique identifier for the cart. + */ + id: string + /** + * ID of the customer to which the cart belongs. + */ + customerId?: string + /** + * The URL of the cart. + */ + url?: string + /** + * The email assigned to this cart. + */ + email?: string + /** + * The date and time when the cart was created. + */ + createdAt: string + /** + * The currency used for this cart */ + currency: { code: string } + /** + * Indicates if taxes are included in the line items. + */ + taxesIncluded: boolean + /** + * List of cart line items. + */ + lineItems: LineItem[] + /** + * The sum of all the pricexs of all the items in the cart. + * Duties, taxes, shipping and discounts excluded. + */ + lineItemsSubtotalPrice: number + /** + * 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. + */ + totalPrice: number + /** + * Discounts that have been applied on the cart. + */ + discounts?: Discount[] +} + +/** + * Base cart item body used for cart mutations + */ +export interface CartItemBody { + /** + * The unique identifier for the product variant. + */ + variantId: string + /** + * 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[] +} + +/** + * Cart Hooks for add, update and remove items from the cart + */ +export type CartHooks = { + getCart: GetCartHook + addItem: AddItemHook + updateItem: UpdateItemHook + removeItem: RemoveItemHook +} + +export type GetCartHook = { + data: Cart | null | undefined + input: {} + fetcherInput: { cartId?: string } + swrState: { isEmpty: boolean } +} + +export type AddItemHook = { + data: Cart | null | undefined + input?: CartItemBody + fetcherInput: CartItemBody + body: { item: CartItemBody } + actionInput: CartItemBody +} + +export type UpdateItemHook = { + data: Cart | null | undefined + input: { item?: LineItem; wait?: number } + fetcherInput: { itemId: string; item: CartItemBody } + body: { itemId: string; item: CartItemBody } + actionInput: CartItemBody & { id: string } +} + +export type RemoveItemHook = { + data: Cart | null | undefined + input: { item?: LineItem } + fetcherInput: { itemId: string } + body: { itemId: string } + actionInput: { id: string } +} + +/** + * Cart API endpoitns & handlers for add, update and remove items from the cart + */ +export type CartSchema = { + endpoint: { + options: {} + handlers: CartHandlers + } +} + +export type CartHandlers = { + getCart: GetCartHandler + addItem: AddItemHandler + updateItem: UpdateItemHandler + removeItem: RemoveItemHandler +} + +export type GetCartHandler = GetCartHook & { + body: { cartId?: string } +} + +export type AddItemHandler = AddItemHook & { + data: Cart | null | undefined + body: { cartId?: string } +} + +export type UpdateItemHandler = UpdateItemHook & { + data: Cart + body: { cartId: string } +} + +export type RemoveItemHandler = RemoveItemHook & { + body: { cartId: string } +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/checkout.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/checkout.ts new file mode 100644 index 00000000000..002cb24c5d2 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/checkout.ts @@ -0,0 +1,114 @@ +import type { AddressFields } from './customer/address' +import type { Card, CardFields } from './customer/card' +import type { LineItem } from './cart' + +export interface Checkout { + /** + * Indicates if the checkout has payment iformation collected. + */ + hasPayment: boolean + /** + * Indicates if the checkout has shipping information collected. + */ + hasShipping: boolean + /** + * The unique identifier for the address that the customer has selected for shipping. + */ + addressId: string + /** + * The list of payment cards that the customer has available. + */ + payments?: Card[] + /** + * The unique identifier of the card that the customer has selected for payment. + */ + cardId?: string + /** + * List of items in the checkout. + */ + lineItems?: LineItem[] +} + +export interface Payment { + paymentMethodId: string +} + +export interface CheckoutBody { + /** + * The email assigned to this cart. + */ + email: string + /** + * The unique identifier for the cart. + */ + cartId?: string + /** + * The Card information. + * @see CardFields + */ + card?: CardFields + /** + * The billing Address information. + * @see AddressFields + */ + billing_address?: AddressFields + /** + * The shipping Address information. + * @see AddressFields + */ + shipping_address?: AddressFields + /** + * The special instructions for the order. + */ + special_instructions?: string + /** + * The list of payments. + */ + payments?: Payment[] + onSuccessAction?: 'orderNext' | 'advance' | 'complete' | null +} + +export type CheckoutTypes = { + checkout: Checkout + checkoutBody: CheckoutBody +} + +export type SubmitCheckoutHook = { + data: T['checkout'] | null + input?: T['checkoutBody'] + fetcherInput: T['checkoutBody'] + body: { item: T['checkoutBody'] } + actionInput: T['checkoutBody'] +} + +export type GetCheckoutHook = { + data: Checkout | null + input: {} + fetcherInput: { cartId?: string } + swrState: { isEmpty: boolean } +} + +export type CheckoutHooks = { + submitCheckout?: SubmitCheckoutHook + getCheckout: GetCheckoutHook +} + +export type GetCheckoutHandler = GetCheckoutHook & { + body: { cartId?: string } +} + +export type SubmitCheckoutHandler = SubmitCheckoutHook & { + body: { cartId: string } +} + +export type CheckoutHandlers = { + getCheckout: GetCheckoutHandler + submitCheckout?: SubmitCheckoutHandler +} + +export type CheckoutSchema = { + endpoint: { + options: {} + handlers: CheckoutHandlers + } +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/common.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/common.ts new file mode 100644 index 00000000000..d63dfc0b988 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/common.ts @@ -0,0 +1,36 @@ +export interface Discount { + /** + * The value of the discount, can be an amount or percentage. + */ + value: number +} + +export interface Measurement { + /** + * The measurement's value. + */ + value: number + /** + * The measurement's unit, such as "KILOGRAMS", "GRAMS", "POUNDS" & "OOUNCES". + */ + unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES' +} + +export interface Image { + /** + * The URL of the image. + */ + url: string + /** + * A word or phrase that describes the content of an image. + */ + alt?: string + /** + * The image's width. + */ + width?: number + /** + * The image's height. + */ + height?: number +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/address.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/address.ts new file mode 100644 index 00000000000..4626a423c18 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/address.ts @@ -0,0 +1,130 @@ +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 state. + */ + state: string + /** + * The customer's billing address country. + */ + country: string + /** + * The customer's phone number. + */ + phone: string +} + +/** + * Hooks for managing a customer's addresses. + */ + +export type GetAddressesHook = { + data: Address[] | null + input: {} + fetcherInput: { cartId?: string } + swrState: { isEmpty: boolean } +} + +export type AddItemHook = { + data: Address | null + input?: AddressFields + fetcherInput: AddressFields + body: { item: AddressFields } + actionInput: AddressFields +} + +export type UpdateItemHook = { + data: Address | null + input: { item?: AddressFields; wait?: number } + fetcherInput: { itemId: string; item: AddressFields } + body: { itemId: string; item: AddressFields } + actionInput: AddressFields & { id: string } +} + +export type RemoveItemHook = { + data: Address | null + input: { item?: Address } + fetcherInput: { itemId: string } + body: { itemId: string } + actionInput: { id: string } +} + +export type CustomerAddressHooks = { + getAddresses: GetAddressesHook + addItem: AddItemHook + updateItem: UpdateItemHook + removeItem: RemoveItemHook +} + +/** + * API endpoints for managing a customer's addresses. + */ + +export type AddItemHandler = AddItemHook & { + body: { cartId: string } +} + +export type UpdateItemHandler = UpdateItemHook & { + body: { cartId: string } +} + +export type RemoveItemHandler = RemoveItemHook & { + body: { cartId: string } +} + +export type CustomerAddressHandlers = { + getAddresses: GetAddressesHook + addItem: AddItemHandler + updateItem: UpdateItemHandler + removeItem: RemoveItemHandler +} + +export type CustomerAddressSchema = { + endpoint: { + options: {} + handlers: CustomerAddressHandlers + } +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/card.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/card.ts new file mode 100644 index 00000000000..37a134de834 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/card.ts @@ -0,0 +1,138 @@ +export interface Card { + /** + * Unique identifier for the card. + */ + id: string + /** + * Masked card number. Contains only the last 4 digits. + * @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 +} + +/** + * Hooks for managing a customer's cards. + */ + +export type GetCardsHook = { + data: Card[] | null + input: {} + fetcherInput: { cartId?: string } + swrState: { isEmpty: boolean } +} + +export type AddItemHook = { + data: Card | null + input?: CardFields + fetcherInput: CardFields + body: { item: CardFields } + actionInput: CardFields +} + +export type UpdateItemHook = { + data: Card | null + input: { item?: CardFields; wait?: number } + fetcherInput: { itemId: string; item: CardFields } + body: { itemId: string; item: CardFields } + actionInput: CardFields & { id: string } +} + +export type RemoveItemHook = { + data: Card | null + input: { item?: Card } + fetcherInput: { itemId: string } + body: { itemId: string } + actionInput: { id: string } +} + +export interface CustomerCardHooks { + getCards: GetCardsHook + addItem: AddItemHook + updateItem: UpdateItemHook + removeItem: RemoveItemHook +} + +/** + * Customer card API handlers. + */ +export type AddItemHandler = AddItemHook & { + body: { cartId: string } +} + +export type UpdateItemHandler = UpdateItemHook & { + body: { cartId: string } +} + +export type RemoveItemHandler = RemoveItemHook & { + body: { cartId: string } +} + +export type CustomerCardHandlers = { + getCards: GetCardsHook + addItem: AddItemHandler + updateItem: UpdateItemHandler + removeItem: RemoveItemHandler +} + +export type CustomerCardSchema = { + endpoint: { + options: {} + handlers: CustomerCardHandlers + } +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/index.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/index.ts new file mode 100644 index 00000000000..89a963a2963 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/customer/index.ts @@ -0,0 +1,54 @@ +export * as Card from './card' +export * as Address from './address' + +export interface Customer { + /** + * The unique identifier for the customer. + */ + id: string + /** + * The customer's first name. + */ + firstName: string + /** + * The customer's last name. + */ + lastName: string + /** + * The customer's email address. + */ + email?: string + /** + * The customer's phone number. + * @optional + */ + phone?: string + /** + * The customer's company name. + */ + company?: string + /** + * The customer's notes. + */ + notes?: string + /** + * Indicates wathever the customer accepts marketing, such as email newsletters. + */ + acceptsMarketing?: boolean +} + +export type CustomerHook = { + data: Customer | null | undefined + fetchData: { customer: Customer } | null +} + +export type CustomerSchema = { + endpoint: { + options: {} + handlers: { + getLoggedInCustomer: { + data: { customer: Customer } | null + } + } + } +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/index.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/index.ts new file mode 100644 index 00000000000..9b6b244126a --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/types/index.ts @@ -0,0 +1,5 @@ +import * as Checkout from './checkout' +import * as Common from './common' +import * as Customer from './customer' + +export type { Checkout, Common, Customer } diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/default-fetcher.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/default-fetcher.ts new file mode 100644 index 00000000000..53312fc9663 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/default-fetcher.ts @@ -0,0 +1,12 @@ +import type { HookFetcherFn } from './types' + +export const SWRFetcher: HookFetcherFn = ({ options, fetch }) => + fetch(options) + +export const mutationFetcher: HookFetcherFn = ({ + input, + options, + fetch, +}) => fetch({ ...options, body: input }) + +export default SWRFetcher diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/define-property.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/define-property.ts new file mode 100644 index 00000000000..e8973522665 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/define-property.ts @@ -0,0 +1,37 @@ +// Taken from https://fettblog.eu/typescript-assertion-signatures/ + +type InferValue = Desc extends { + get(): any + value: any +} + ? never + : Desc extends { value: infer T } + ? Record + : Desc extends { get(): infer T } + ? Record + : never + +type DefineProperty< + Prop extends PropertyKey, + Desc extends PropertyDescriptor +> = Desc extends { writable: any; set(val: any): any } + ? never + : Desc extends { writable: any; get(): any } + ? never + : Desc extends { writable: false } + ? Readonly> + : Desc extends { writable: true } + ? InferValue + : Readonly> + +export default function defineProperty< + Obj extends object, + Key extends PropertyKey, + PDesc extends PropertyDescriptor +>( + obj: Obj, + prop: Key, + val: PDesc +): asserts obj is Obj & DefineProperty { + Object.defineProperty(obj, prop, val) +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/types.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/types.ts new file mode 100644 index 00000000000..9e71d44c68d --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/types.ts @@ -0,0 +1,153 @@ +/* + Forked from https://github.com/vercel/commerce/tree/main/packages/commerce/src + Changes: Added CommerceExtraFeatures and provider to HookFetcherContext +*/ +import type { SWRConfiguration } from 'swr' +import type { CommerceError } from '@plasmicpkgs/commerce' +import type { ResponseState } from './use-data' +import { Provider } from '@plasmicpkgs/commerce' + +/** + * Returns the properties in T with the properties in type K, overriding properties defined in T + */ +export type Override = Omit & K + +/** + * Returns the properties in T with the properties in type K changed from optional to required + */ +export type PickRequired = Omit & { + [P in K]-?: NonNullable +} + +/** + * Core fetcher added by CommerceProvider + */ +export type Fetcher = ( + options: FetcherOptions +) => T | Promise + +export type FetcherOptions = { + url?: string + query?: string + method?: string + variables?: any + body?: Body +} + +export type HookFetcher = ( + options: HookFetcherOptions | null, + input: Input, + fetch: (options: FetcherOptions) => Promise +) => Data | Promise + +export type HookFetcherFn = ( + context: HookFetcherContext +) => H['data'] | Promise + +export type HookFetcherContext = { + options: HookFetcherOptions + input: H['fetcherInput'] + fetch: < + T = H['fetchData'] extends {} | null ? H['fetchData'] : any, + B = H['body'] + >( + options: FetcherOptions + ) => Promise + provider?: Provider +} + +export type HookFetcherOptions = { method?: string } & ( + | { query: string; url?: string } + | { query?: string; url: string } +) + +export type HookInputValue = string | number | boolean | undefined + +export type HookSWRInput = [string, HookInputValue][] + +export type HookFetchInput = { [k: string]: HookInputValue } + +export type HookFunction< + Input extends { [k: string]: unknown } | undefined, + T +> = keyof Input extends never + ? () => T + : Partial extends Input + ? (input?: Input) => T + : (input?: Input) => T + +export type HookSchemaBase = { + // Data obj returned by the hook + data: any + // Input expected by the hook + input?: {} + // Input expected before doing a fetch operation (aka fetch handler) + fetcherInput?: {} + // Body object expected by the fetch operation + body?: {} + // Data returned by the fetch operation + fetchData?: any +} + +export type SWRHookSchemaBase = HookSchemaBase & { + // Custom state added to the response object of SWR + swrState?: {} + // Instances of MutationSchemaBase that the hook returns for better DX + mutations?: Record['useHook']>> +} + +export type MutationSchemaBase = HookSchemaBase & { + // Input expected by the action returned by the hook + actionInput?: {} +} + +/** + * Generates a SWR hook handler based on the schema of a hook + */ +export type SWRHook = { + useHook( + context: SWRHookContext + ): HookFunction< + H['input'] & { swrOptions?: SwrOptions }, + ResponseState & H['swrState'] & H['mutations'] + > + fetchOptions: HookFetcherOptions + fetcher?: HookFetcherFn +} + +export type SWRHookContext = { + useData(context?: { + input?: HookFetchInput | HookSWRInput + swrOptions?: SwrOptions + }): ResponseState +} + +/** + * Generates a mutation hook handler based on the schema of a hook + */ +export type MutationHook = { + useHook( + context: MutationHookContext + ): HookFunction< + H['input'], + HookFunction> + > + fetchOptions: HookFetcherOptions + fetcher?: HookFetcherFn +} + +export type MutationHookContext = { + fetch: keyof H['fetcherInput'] extends never + ? () => H['data'] | Promise + : Partial extends H['fetcherInput'] + ? (context?: { + input?: H['fetcherInput'] + }) => H['data'] | Promise + : (context: { input: H['fetcherInput'] }) => H['data'] | Promise +} + +export type SwrOptions = SWRConfiguration< + Data, + CommerceError, + HookFetcher +> diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/use-data.tsx b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/use-data.tsx new file mode 100644 index 00000000000..edd74273648 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/use-data.tsx @@ -0,0 +1,86 @@ +/* + Forked from https://github.com/vercel/commerce/tree/main/packages/commerce/src + Changes: Replaced useSWR for useMutablePlasmicQueryData and add provider to useData +*/ +import { useMutablePlasmicQueryData } from '@plasmicapp/query' +import { SWRResponse } from 'swr' +import { Provider } from '@plasmicpkgs/commerce' +import defineProperty from './define-property' +import { CommerceError } from '@plasmicpkgs/commerce' +import type { + Fetcher, + HookFetcherFn, + HookFetcherOptions, + HookFetchInput, + HookSWRInput, + SWRHookSchemaBase, + SwrOptions, +} from './types' + +export type ResponseState = SWRResponse & { + isLoading: boolean +} + +export type UseData = ( + options: { + fetchOptions: HookFetcherOptions + fetcher: HookFetcherFn + }, + input: HookFetchInput | HookSWRInput, + fetcherFn: Fetcher, + swrOptions?: SwrOptions, + provider?: Provider +) => ResponseState + +const useData: UseData = (options, input, fetcherFn, swrOptions, provider) => { + const hookInput = Array.isArray(input) ? input : Object.entries(input) + const fetcher = async ( + url: string, + query?: string, + method?: string, + ...args: any[] + ) => { + try { + return await options.fetcher({ + options: { url, query, method }, + // Transform the input array into an object + input: args.reduce((obj, val, i) => { + obj[hookInput[i][0]!] = val + return obj + }, {}), + fetch: fetcherFn, + provider, + }) + } catch (error) { + // SWR will not log errors, but any error that's not an instance + // of CommerceError is not welcomed by this hook + if (!(error instanceof CommerceError)) { + console.error(error) + } + throw error + } + } + const response = useMutablePlasmicQueryData( + () => { + const opts = options.fetchOptions + return opts + ? [opts.url, opts.query, opts.method, ...hookInput.map((e) => e[1])] + : null + }, + fetcher, + swrOptions + ) + + if (!('isLoading' in response)) { + defineProperty(response, 'isLoading', { + get() { + return response.data === undefined + }, + enumerable: true, + }) + } + + return response as typeof response & { isLoading: boolean } +} + +export default useData diff --git a/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/use-hook.ts b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/use-hook.ts new file mode 100644 index 00000000000..9af47ef9544 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/commerce/utils/use-hook.ts @@ -0,0 +1,68 @@ +/* + Forked from https://github.com/vercel/commerce/tree/main/packages/commerce/src + Changes: Add provider to useSWRHook and useMutationHook +*/ +import { useCallback } from 'react' +import { Provider, useCommerce } from '@plasmicpkgs/commerce' +import type { MutationHook, PickRequired, SWRHook } from './types' +import useData from './use-data' + +export function useFetcher() { + const { providerRef, fetcherRef } = useCommerce() + return providerRef.current.fetcher ?? fetcherRef.current +} + +export function useProvider() { + const { providerRef } = useCommerce() + return providerRef.current +} + +export function useHook< + P extends Provider, + H extends MutationHook | SWRHook +>(fn: (provider: P) => H) { + const { providerRef } = useCommerce

() + const provider = providerRef.current + return fn(provider) +} + +export function useSWRHook>( + hook: PickRequired +) { + const fetcher = useFetcher() + const provider = useProvider() + + return hook.useHook({ + useData(ctx) { + const response = useData( + hook, + ctx?.input ?? [], + fetcher, + ctx?.swrOptions, + provider + ) + return response + }, + }) +} + +export function useMutationHook>( + hook: PickRequired +) { + const fetcher = useFetcher() + const provider = useProvider() + + return hook.useHook({ + fetch: useCallback( + ({ input } = {}) => { + return hook.fetcher({ + input, + options: hook.fetchOptions, + fetch: fetcher, + provider, + }) + }, + [fetcher, hook.fetchOptions] + ), + }) +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/index.tsx b/platform/canvas-packages/internal_pkgs/spree/src/index.tsx index 826d04b865d..996147f23ba 100755 --- a/platform/canvas-packages/internal_pkgs/spree/src/index.tsx +++ b/platform/canvas-packages/internal_pkgs/spree/src/index.tsx @@ -1,14 +1,13 @@ -import { Registerable } from "./registerable"; -import { - registerCommerceProvider, - CommerceProviderComponent, -} from "./registerCommerceProvider"; -export * from "./registerable"; +import { Registerable } from './registerable' +import { registerCommerceProvider } from './registerCommerceProvider' +import { registerCheckoutProvider } from './registerCheckoutProvider' +export * from './registerable' +export * from './registerCheckoutProvider' +export * from './registerCommerceProvider' -export * from "./spree"; +export * from './spree' export function registerAll(loader?: Registerable) { - registerCommerceProvider(loader); + registerCommerceProvider(loader) + registerCheckoutProvider(loader) } - -export { registerCommerceProvider, CommerceProviderComponent }; diff --git a/platform/canvas-packages/internal_pkgs/spree/src/provider.ts b/platform/canvas-packages/internal_pkgs/spree/src/provider.ts index de765f62eea..1db35db6f8d 100755 --- a/platform/canvas-packages/internal_pkgs/spree/src/provider.ts +++ b/platform/canvas-packages/internal_pkgs/spree/src/provider.ts @@ -7,6 +7,8 @@ import { handler as useSearch } from './product/use-search' import { handler as useProduct } from './product/use-product' import { handler as useCategories } from './site/use-categories' import { handler as useBrands } from './site/use-brands' +import { handler as useCheckout } from './checkout/use-checkout' +import { handler as useSubmitCheckout } from './checkout/use-submit-checkout' import { requireConfigValue } from './isomorphic-config' import type { Fetcher, FetcherOptions } from '@plasmicpkgs/commerce' @@ -19,6 +21,7 @@ export const getSpreeProvider = (apiHost: string) => { cart: { useCart, useAddItem, useUpdateItem, useRemoveItem }, products: { useSearch, useProduct }, site: { useCategories, useBrands }, + checkout: { useCheckout, useSubmitCheckout }, } } @@ -34,4 +37,8 @@ export type SpreeProvider = { } products: { useSearch: typeof useSearch; useProduct: typeof useProduct } site: { useCategories: typeof useCategories; useBrands: typeof useBrands } + checkout: { + useCheckout: typeof useCheckout + useSubmitCheckout: typeof useSubmitCheckout + } } diff --git a/platform/canvas-packages/internal_pkgs/spree/src/registerCheckoutProvider.tsx b/platform/canvas-packages/internal_pkgs/spree/src/registerCheckoutProvider.tsx new file mode 100644 index 00000000000..8fe696cbeeb --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/registerCheckoutProvider.tsx @@ -0,0 +1,213 @@ +import registerComponent, { + ComponentMeta, +} from '@plasmicapp/host/registerComponent' +import { Registerable } from './registerable' +import React, { useMemo } from 'react' +import useCheckout from './checkout/use-checkout' +import { + DataProvider, + GlobalActionDict, + GlobalActionsProvider, +} from '@plasmicapp/host' +import useSubmitCheckout from './commerce/checkout/use-submit-checkout' +import type { AddressFields } from './commerce/types/customer/address' +import { Payment } from './commerce/types/checkout' +import { globalActionsRegistrations as baseGlobalActionsRegistrations } from '@plasmicpkgs/commerce' +import { GlobalActionRegistration } from '@plasmicapp/host/registerGlobalContext' + +export const checkoutProviderMeta: ComponentMeta< + React.PropsWithChildren +> = { + name: 'plasmic-commerce-spree-checkout', + displayName: 'Checkout Provider', + description: + 'Use this to create bespoke checkout UI. Inside Checkout Provider, use dynamic values to access checkout data.', + props: { + children: 'slot', + }, + providesData: true, + importPath: 'commerce-spree', + importName: 'CheckoutProvider', +} + +interface CheckoutActions extends GlobalActionDict { + submitCheckout: ( + email: string, + special_instructions: string, + billing_address: AddressFields, + shipping_address: AddressFields, + payments: Payment[], + onSuccessAction: 'orderNext' | 'advance' | 'complete' | null + ) => void +} + +export function CheckoutActionsProvider( + props: React.PropsWithChildren<{ + globalContextName: string + }> +) { + const submitCheckout = useSubmitCheckout() + const actions: CheckoutActions = useMemo( + () => ({ + submitCheckout( + email: string, + special_instructions: string, + billing_address: AddressFields, + shipping_address: AddressFields, + payments: Payment[], + onSuccessAction: 'orderNext' | 'advance' | 'complete' | null + ) { + submitCheckout({ + email, + special_instructions, + billing_address, + shipping_address, + payments, + onSuccessAction, + }) + }, + }), + [submitCheckout] + ) + + return ( + + {props.children} + + ) +} + +export function CheckoutProvider(props: React.PropsWithChildren) { + const { data } = useCheckout() + return ( + + {props.children} + + ) +} + +export function registerCheckoutProvider( + loader?: Registerable, + customCheckoutMeta?: ComponentMeta> +) { + const doRegisterComponent: typeof registerComponent = (...args) => + loader ? loader.registerComponent(...args) : registerComponent(...args) + doRegisterComponent( + CheckoutProvider, + customCheckoutMeta ?? checkoutProviderMeta + ) +} + +const addressFields: Record = { + type: { + displayName: 'Type', + type: { + type: 'choice', + multiSelect: false, + options: [ + { value: 'billing', label: 'Billing' }, + { value: 'shipping', label: 'Shipping' }, + ], + }, + }, + firstName: { + displayName: 'First name', + type: 'string', + }, + lastName: { + displayName: 'Last name', + type: 'string', + }, + company: { + displayName: 'Company', + type: 'string', + }, + streetNumber: { + displayName: 'Street number', + type: 'string', + }, + apartments: { + displayName: 'Apartments', + type: 'string', + }, + zipCode: { + displayName: 'Zip code', + type: 'string', + }, + city: { + displayName: 'City', + type: 'string', + }, + state: { + displayName: 'State', + type: 'string', + }, + country: { + displayName: 'Country', + type: 'string', + }, + phone: { + displayName: 'Phone', + type: 'string', + }, +} + +export const globalActionsRegistrations: Record< + string, + GlobalActionRegistration +> = { + ...baseGlobalActionsRegistrations, + submitCheckout: { + displayName: 'Submit checkout', + parameters: [ + { + name: 'email', + displayName: 'Email', + type: 'string', + }, + { + name: 'special_instructions', + displayName: 'Special instructions', + type: 'string', + }, + { + name: 'billing_address', + displayName: 'Billing address', + type: { + type: 'object', + fields: addressFields, + }, + }, + { + name: 'shipping_address', + displayName: 'Shipping address', + type: { + type: 'object', + fields: addressFields, + }, + }, + { + name: 'payments', + displayName: 'Payments', + type: 'object', + }, + { + name: 'onSuccessAction', + displayName: 'On success action', + type: { + type: 'choice', + multiSelect: false, + options: [ + { value: 'orderNext', label: 'Next' }, + { value: 'advance', label: 'Advance' }, + { value: 'complete', label: 'Complete' }, + { value: 'null', label: 'None' }, + ], + }, + }, + ], + }, +} diff --git a/platform/canvas-packages/internal_pkgs/spree/src/registerCommerceProvider.tsx b/platform/canvas-packages/internal_pkgs/spree/src/registerCommerceProvider.tsx index 349c60ecdd7..bf325b18493 100644 --- a/platform/canvas-packages/internal_pkgs/spree/src/registerCommerceProvider.tsx +++ b/platform/canvas-packages/internal_pkgs/spree/src/registerCommerceProvider.tsx @@ -1,49 +1,52 @@ -import { GlobalContextMeta } from "@plasmicapp/host"; -import registerGlobalContext from "@plasmicapp/host/registerGlobalContext"; +import { GlobalContextMeta } from '@plasmicapp/host' +import registerGlobalContext from '@plasmicapp/host/registerGlobalContext' +import { CartActionsProvider } from '@plasmicpkgs/commerce' +import React from 'react' +import { Registerable } from './registerable' +import { getCommerceProvider } from './spree' import { - CartActionsProvider, + CheckoutActionsProvider, globalActionsRegistrations, -} from "@plasmicpkgs/commerce"; -import React from "react"; -import { Registerable } from "./registerable"; -import { getCommerceProvider } from "./spree"; +} from './registerCheckoutProvider' interface CommerceProviderProps { - children?: React.ReactNode; - apiHost: string; + children?: React.ReactNode + apiHost: string } -const globalContextName = "plasmic-commerce-spree-provider"; +const globalContextName = 'plasmic-commerce-spree-provider' export const commerceProviderMeta: GlobalContextMeta = { name: globalContextName, - displayName: "Spree Provider", + displayName: 'Spree Provider', props: { apiHost: { - type: "string", - defaultValue: "https://olitt.shop", + type: 'string', + defaultValue: 'https://olitt.shop', }, }, ...{ globalActions: globalActionsRegistrations }, - importPath: "commerce-spree", - importName: "CommerceProviderComponent", -}; + importPath: 'commerce-spree', + importName: 'CommerceProviderComponent', +} export function CommerceProviderComponent(props: CommerceProviderProps) { - const { apiHost, children } = props; + const { apiHost, children } = props const CommerceProvider = React.useMemo( () => getCommerceProvider(apiHost), [apiHost] - ); + ) return ( - {children} + + {children} + - ); + ) } export function registerCommerceProvider( @@ -53,9 +56,9 @@ export function registerCommerceProvider( const doRegisterComponent: typeof registerGlobalContext = (...args) => loader ? loader.registerGlobalContext(...args) - : registerGlobalContext(...args); + : registerGlobalContext(...args) doRegisterComponent( CommerceProviderComponent, customCommerceProviderMeta ?? commerceProviderMeta - ); + ) } diff --git a/platform/canvas-packages/internal_pkgs/spree/src/spree.tsx b/platform/canvas-packages/internal_pkgs/spree/src/spree.tsx index aba599f76b9..35a01f18a3a 100644 --- a/platform/canvas-packages/internal_pkgs/spree/src/spree.tsx +++ b/platform/canvas-packages/internal_pkgs/spree/src/spree.tsx @@ -1,8 +1,6 @@ -import { - getCommerceProvider as getCoreCommerceProvider, - useCommerce as useCoreCommerce, -} from '@plasmicpkgs/commerce' +import { useCommerce as useCoreCommerce } from '@plasmicpkgs/commerce' import { getSpreeProvider, SpreeProvider } from './provider' +import { getCommerceProvider as getCoreCommerceProvider } from '@plasmicpkgs/commerce' export type { SpreeProvider } @@ -10,4 +8,3 @@ export const useCommerce = () => useCoreCommerce() export const getCommerceProvider = (apiHost: string) => getCoreCommerceProvider(getSpreeProvider(apiHost)) - diff --git a/platform/canvas-packages/internal_pkgs/spree/src/utils/get-cart.ts b/platform/canvas-packages/internal_pkgs/spree/src/utils/get-cart.ts new file mode 100644 index 00000000000..d48650f2747 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/utils/get-cart.ts @@ -0,0 +1,80 @@ +import type { GraphQLFetcherResult } from '../types' +import { FetcherError, HookFetcherContext } from '@plasmicpkgs/commerce' +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 ensureIToken from './tokens/ensure-itoken' +import createEmptyCart from './create-empty-cart' +import isLoggedIn from './tokens/is-logged-in' +import { setCartToken } from './tokens/cart-token' +import { requireConfigValue } from '../isomorphic-config' + +const imagesSize = requireConfigValue('imagesSize') as string +const imagesQuality = requireConfigValue('imagesQuality') as number + +const getCart = async ( + fetch: HookFetcherContext<{ + data: any + }>['fetch'] +): Promise => { + let spreeCartResponse: IOrder | null + + const token: IToken | undefined = ensureIToken() + + if (!token) { + spreeCartResponse = null + } else { + try { + const { data: spreeCartShowSuccessResponse } = await fetch< + GraphQLFetcherResult + >({ + variables: { + methodPath: 'cart.show', + arguments: [ + token, + { + 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(','), + image_transformation: { + quality: imagesQuality, + size: imagesSize, + }, + }, + ], + }, + }) + + spreeCartResponse = spreeCartShowSuccessResponse + } catch (fetchCartError) { + if ( + !(fetchCartError instanceof FetcherError) || + fetchCartError.status !== 404 + ) { + throw fetchCartError + } + + spreeCartResponse = null + } + } + + if (!spreeCartResponse || spreeCartResponse?.data.attributes.completed_at) { + const { data: spreeCartCreateSuccessResponse } = await createEmptyCart( + fetch + ) + + spreeCartResponse = spreeCartCreateSuccessResponse + + if (!isLoggedIn()) { + setCartToken(spreeCartResponse.data.attributes.token) + } + } + return spreeCartResponse +} + +export default getCart diff --git a/platform/canvas-packages/internal_pkgs/spree/src/utils/submit-checkout.ts b/platform/canvas-packages/internal_pkgs/spree/src/utils/submit-checkout.ts new file mode 100644 index 00000000000..b9754d6aa79 --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/utils/submit-checkout.ts @@ -0,0 +1,149 @@ +import type { GraphQLFetcherResult } from '../types' +import { + FetcherError, + HookFetcherContext, + ValidationError, +} from '@plasmicpkgs/commerce' +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 ensureIToken from './tokens/ensure-itoken' +import createEmptyCart from './create-empty-cart' +import isLoggedIn from './tokens/is-logged-in' +import { setCartToken } from './tokens/cart-token' +import { IPayment } from '@spree/storefront-api-v2-sdk/types/interfaces/attributes/Payment' +import { OrderUpdate } from '@spree/storefront-api-v2-sdk/types/interfaces/Checkout' +import normalizeCart from './normalizations/normalize-cart' +import type { AddressFields } from '../commerce/types/customer/address' +import { Checkout, CheckoutBody } from '../commerce/types/checkout' + +function buildAddress(address: AddressFields) { + return { + firstname: address?.firstName, + lastname: address?.lastName, + address1: address?.streetNumber, + address2: address?.apartments, + city: address?.city, + zipcode: address?.zipCode, + phone: address?.phone, + state_name: address?.state, + country_iso: address?.country, + } +} + +const submitCheckout = async ( + fetch: HookFetcherContext<{ + data: any + }>['fetch'], + input: CheckoutBody +): Promise => { + let spreeCartResponse: IOrder | null + + const { + email, + special_instructions, + billing_address, + shipping_address, + payments, + onSuccessAction, + } = input + + if (!email) { + throw new ValidationError({ + message: 'email needs to be provided.', + }) + } + + let token: IToken | undefined = ensureIToken() + + if (!token) { + const { data: spreeCartCreateSuccessResponse } = await createEmptyCart( + fetch + ) + + setCartToken(spreeCartCreateSuccessResponse.data.attributes.token) + token = ensureIToken() + } + + try { + const payments_attributes = payments?.map((payment) => ({ + payment_method_id: payment.paymentMethodId, + })) as IPayment[] + + const includeParams = [ + '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 orderUpdateParameters: OrderUpdate = { + order: { + email, + special_instructions, + bill_address_attributes: buildAddress(billing_address), + ship_address_attributes: buildAddress(shipping_address), + payments_attributes, + }, + include: includeParams, + } + + const { data: spreeSuccessResponse } = await fetch< + GraphQLFetcherResult + >({ + variables: { + methodPath: 'checkout.orderUpdate', + arguments: [token, orderUpdateParameters], + }, + }) + + if (onSuccessAction) { + const { data: checkoutActionResponse } = await fetch< + GraphQLFetcherResult + >({ + variables: { + methodPath: `checkout.${onSuccessAction}`, + arguments: [token], + include: includeParams, + }, + }) + spreeCartResponse = checkoutActionResponse + } else { + spreeCartResponse = spreeSuccessResponse + } + } catch (updateItemError) { + if ( + updateItemError instanceof FetcherError && + updateItemError.status === 404 + ) { + const { data: spreeRetroactiveCartCreateSuccessResponse } = + await createEmptyCart(fetch) + + if (!isLoggedIn()) { + setCartToken( + spreeRetroactiveCartCreateSuccessResponse.data.attributes.token + ) + } + + // Return an empty cart. The user has to update the item again. + // This is going to be a rare situation. + + spreeCartResponse = spreeRetroactiveCartCreateSuccessResponse + } + + throw updateItemError + } + const cart = normalizeCart(spreeCartResponse, spreeCartResponse.data) + return { + hasPayment: false, + hasShipping: false, + addressId: null, + payments: [], + cardId: null, + lineItems: cart.lineItems, + } +} + +export default submitCheckout diff --git a/platform/canvas-packages/internal_pkgs/spree/src/utils/types.ts b/platform/canvas-packages/internal_pkgs/spree/src/utils/types.ts new file mode 100644 index 00000000000..7b841dee3ef --- /dev/null +++ b/platform/canvas-packages/internal_pkgs/spree/src/utils/types.ts @@ -0,0 +1,153 @@ +import type { SWRConfiguration } from 'swr' +import type { CommerceError } from '@plasmicpkgs/commerce' +import type { ResponseState } from '../commerce/utils/use-data' +import { Provider } from '@plasmicpkgs/commerce' + +/** + * Returns the properties in T with the properties in type K, overriding properties defined in T + */ +export type Override = Omit & K + +/** + * Returns the properties in T with the properties in type K changed from optional to required + */ +export type PickRequired = Omit & { + [P in K]-?: NonNullable +} + +/** + * Core fetcher added by CommerceProvider + */ +export type Fetcher = ( + options: FetcherOptions +) => T | Promise + +export type FetcherOptions = { + url?: string + query?: string + method?: string + variables?: any + body?: Body +} + +export type HookFetcher = ( + options: HookFetcherOptions | null, + input: Input, + fetch: (options: FetcherOptions) => Promise +) => Data | Promise + +export type HookFetcherFn = ( + context: HookFetcherContext +) => H['data'] | Promise + +export type HookFetcherContext = { + options: HookFetcherOptions + input: H['fetcherInput'] + fetch: < + T = H['fetchData'] extends {} | null ? H['fetchData'] : any, + B = H['body'] + >( + options: FetcherOptions + ) => Promise + provider?: Provider +} + +export type HookFetcherOptions = { method?: string } & ( + | { query: string; url?: string } + | { query?: string; url: string } +) + +export type HookInputValue = string | number | boolean | undefined + +export type HookSWRInput = [string, HookInputValue][] + +export type HookFetchInput = { [k: string]: HookInputValue } + +export type HookFunction< + Input extends { [k: string]: unknown } | undefined, + T +> = keyof Input extends never + ? () => T + : Partial extends Input + ? (input?: Input) => T + : (input: Input) => T + +export type HookSchemaBase = { + // Data obj returned by the hook + data: any + // Input expected by the hook + input?: {} + // Input expected before doing a fetch operation (aka fetch handler) + fetcherInput?: {} + // Body object expected by the fetch operation + body?: {} + // Data returned by the fetch operation + fetchData?: any +} + +export type SWRHookSchemaBase = HookSchemaBase & { + // Custom state added to the response object of SWR + swrState?: {} + // Instances of MutationSchemaBase that the hook returns for better DX + mutations?: Record['useHook']>> +} + +export type MutationSchemaBase = HookSchemaBase & { + // Input expected by the action returned by the hook + actionInput?: {} +} + +/** + * Generates a SWR hook handler based on the schema of a hook + */ +export type SWRHook = { + useHook( + context: SWRHookContext + ): HookFunction< + H['input'] & { swrOptions?: SwrOptions }, + ResponseState & H['swrState'] & H['mutations'] + > + fetchOptions: HookFetcherOptions + fetcher?: HookFetcherFn +} + +export type SWRHookContext = { + useData(context?: { + input?: HookFetchInput | HookSWRInput + swrOptions?: SwrOptions + }): ResponseState +} + +/** + * Generates a mutation hook handler based on the schema of a hook + */ +export type MutationHook = { + useHook( + context: MutationHookContext + ): HookFunction< + H['input'], + HookFunction> + > + fetchOptions: HookFetcherOptions + fetcher?: HookFetcherFn +} + +export type MutationHookContext = { + fetch: keyof H['fetcherInput'] extends never + ? () => H['data'] | Promise + : Partial extends H['fetcherInput'] + ? (context?: { + input?: H['fetcherInput'] + }) => H['data'] | Promise + : (context: { input: H['fetcherInput'] }) => H['data'] | Promise +} + +export type SwrOptions = SWRConfiguration< + Data, + CommerceError, + HookFetcher +> + +export type CommerceExtraFeatures = { + includeSubCategories?: boolean +} diff --git a/platform/canvas-packages/internal_pkgs/spree/yarn.lock b/platform/canvas-packages/internal_pkgs/spree/yarn.lock index 4dc3b63fc5e..76fbcad09d5 100644 --- a/platform/canvas-packages/internal_pkgs/spree/yarn.lock +++ b/platform/canvas-packages/internal_pkgs/spree/yarn.lock @@ -1612,6 +1612,11 @@ agentkeepalive "^4.2.1" debug "^4.3.3" +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + "@zeit/dns-cached-resolve@^2.1.2": version "2.1.2" resolved "https://registry.npmjs.org/@zeit/dns-cached-resolve/-/dns-cached-resolve-2.1.2.tgz" @@ -2207,7 +2212,7 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -call-bind-apply-helpers@^1.0.0: +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz" integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== @@ -2225,6 +2230,14 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7, call-bin get-intrinsic "^1.2.4" set-function-length "^1.2.2" +call-bound@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.3.tgz#41cfd032b593e39176a71533ab4f384aa04fd681" + integrity sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA== + dependencies: + call-bind-apply-helpers "^1.0.1" + get-intrinsic "^1.2.6" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" @@ -2274,7 +2287,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2307,6 +2320,11 @@ ci-info@^2.0.0: resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.7.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + ci-job-number@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/ci-job-number/-/ci-job-number-1.2.2.tgz" @@ -2488,7 +2506,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: +cross-spawn@^7.0.0, cross-spawn@^7.0.3: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -2712,6 +2730,15 @@ dunder-proto@^1.0.0: es-errors "^1.3.0" gopd "^1.2.0" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" @@ -3498,6 +3525,13 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz" @@ -3632,11 +3666,35 @@ get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: has-symbols "^1.1.0" hasown "^2.0.2" +get-intrinsic@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.7.tgz#dcfcb33d3272e15f445d15124bc0a216189b9044" + integrity sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + function-bind "^1.1.2" + get-proto "^1.0.0" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-proto@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stdin@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz" @@ -3743,7 +3801,7 @@ gopd@^1.0.1, gopd@^1.1.0, gopd@^1.2.0: resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -4857,6 +4915,17 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stable-stringify@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz#addb683c2b78014d0b78d704c2fcbdf0695a60e2" + integrity sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + isarray "^2.0.5" + jsonify "^0.0.1" + object-keys "^1.1.1" + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" @@ -4890,6 +4959,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonify@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" + integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz" @@ -4929,6 +5003,13 @@ kind-of@^6.0.2: resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + kleur@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" @@ -5097,6 +5178,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" @@ -5454,6 +5540,14 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + optionator@^0.8.1, optionator@^0.8.3: version "0.8.3" resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" @@ -5554,6 +5648,27 @@ pascalcase@^0.1.1: resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz" integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== +patch-package@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.0.tgz#d191e2f1b6e06a4624a0116bcb88edd6714ede61" + integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^4.1.2" + ci-info "^3.7.0" + cross-spawn "^7.0.3" + find-yarn-workspace-root "^2.0.0" + fs-extra "^9.0.0" + json-stable-stringify "^1.0.2" + klaw-sync "^6.0.0" + minimist "^1.2.6" + open "^7.4.2" + rimraf "^2.6.3" + semver "^7.5.3" + slash "^2.0.0" + tmp "^0.0.33" + yaml "^2.2.2" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" @@ -6056,6 +6171,13 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" +rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" @@ -6343,6 +6465,11 @@ size-limit@^7.0.8: nanospinner "^1.0.0" picocolors "^1.0.0" +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + slash@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" @@ -7384,6 +7511,11 @@ yaml@^1.7.2: resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98" + integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA== + yargs-parser@18.x, yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" diff --git a/platform/canvas-packages/package.json b/platform/canvas-packages/package.json index 511fc6c0b89..97e5494d31e 100644 --- a/platform/canvas-packages/package.json +++ b/platform/canvas-packages/package.json @@ -70,7 +70,7 @@ "axios": "^1.5.1", "chart.js": "^4.2.1", "classnames": "^2.3.2", - "commerce-spree": "^0.0.30", + "commerce-spree": "^0.1.0", "copy-to-clipboard": "^3.3.3", "date-fns": "^2.30.0", "dayjs": "^1.11.10", diff --git a/platform/canvas-packages/yarn.lock b/platform/canvas-packages/yarn.lock index 2d588912cb5..1025b21d304 100644 --- a/platform/canvas-packages/yarn.lock +++ b/platform/canvas-packages/yarn.lock @@ -7284,10 +7284,10 @@ commander@^7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commerce-spree@^0.0.30: - version "0.0.30" - resolved "https://registry.yarnpkg.com/commerce-spree/-/commerce-spree-0.0.30.tgz#d44248f6a109cd1bd9a7403bdd06704a1f0c2a98" - integrity sha512-NQHylP/9pwNMkl0HYfYXNNh6G7b0QXB9wwQHWrYRrgDouAF1sP3vcIAKthOba1EwCAkZnhPc/CKBstQAC0sDfw== +commerce-spree@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/commerce-spree/-/commerce-spree-0.1.0.tgz#7986bf8cdfb1fd1fbf58dcc7788b65c17e27b4ab" + integrity sha512-Yu1dt5E7jo56+ycEe5Bw1wvarGH1JbsdJKkQRm2gijdz1gQZ+3LTeEncNzwi4ZzCFyIQJeyNweEOsvgykGI/sQ== dependencies: "@plasmicpkgs/commerce" "0.0.205" "@spree/storefront-api-v2-sdk" "^5.1.1" diff --git a/platform/loader-bundle-env/package.json b/platform/loader-bundle-env/package.json index 8a86bde883a..ac40f9eb731 100644 --- a/platform/loader-bundle-env/package.json +++ b/platform/loader-bundle-env/package.json @@ -26,7 +26,7 @@ "@plasmicpkgs/commerce-saleor": "^0.0.170", "@plasmicpkgs/commerce-shopify": "^0.0.213", "@plasmicpkgs/commerce-swell": "^0.0.215", - "commerce-spree": "^0.0.30", + "commerce-spree": "^0.1.0", "@plasmicpkgs/framer-motion": "^0.0.206", "@plasmicpkgs/lottie-react": "^0.0.199", "@plasmicpkgs/plasmic-basic-components": "^0.0.231", diff --git a/platform/loader-bundle-env/yarn.lock b/platform/loader-bundle-env/yarn.lock index 641486ce58e..f5277eac5bb 100644 --- a/platform/loader-bundle-env/yarn.lock +++ b/platform/loader-bundle-env/yarn.lock @@ -6664,10 +6664,10 @@ combined-stream@^1.0.6, combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commerce-spree@^0.0.30: - version "0.0.30" - resolved "https://registry.yarnpkg.com/commerce-spree/-/commerce-spree-0.0.30.tgz#d44248f6a109cd1bd9a7403bdd06704a1f0c2a98" - integrity sha512-NQHylP/9pwNMkl0HYfYXNNh6G7b0QXB9wwQHWrYRrgDouAF1sP3vcIAKthOba1EwCAkZnhPc/CKBstQAC0sDfw== +commerce-spree@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/commerce-spree/-/commerce-spree-0.1.0.tgz#7986bf8cdfb1fd1fbf58dcc7788b65c17e27b4ab" + integrity sha512-Yu1dt5E7jo56+ycEe5Bw1wvarGH1JbsdJKkQRm2gijdz1gQZ+3LTeEncNzwi4ZzCFyIQJeyNweEOsvgykGI/sQ== dependencies: "@plasmicpkgs/commerce" "0.0.205" "@spree/storefront-api-v2-sdk" "^5.1.1" diff --git a/platform/wab/.tmp/loader-bundle-env/package.json b/platform/wab/.tmp/loader-bundle-env/package.json index 3711ba6a779..1b614e4fdc7 100644 --- a/platform/wab/.tmp/loader-bundle-env/package.json +++ b/platform/wab/.tmp/loader-bundle-env/package.json @@ -26,7 +26,7 @@ "@plasmicpkgs/commerce-saleor": "^0.0.169", "@plasmicpkgs/commerce-shopify": "^0.0.212", "@plasmicpkgs/commerce-swell": "^0.0.214", - "commerce-spree": "^0.0.30", + "commerce-spree": "^0.1.0", "@plasmicpkgs/framer-motion": "^0.0.205", "@plasmicpkgs/lottie-react": "^0.0.197", "@plasmicpkgs/plasmic-basic-components": "^0.0.230", diff --git a/platform/wab/.tmp/loader-bundle-env/yarn.lock b/platform/wab/.tmp/loader-bundle-env/yarn.lock index 7e0089e0cac..490130ef6e3 100644 --- a/platform/wab/.tmp/loader-bundle-env/yarn.lock +++ b/platform/wab/.tmp/loader-bundle-env/yarn.lock @@ -6651,10 +6651,10 @@ combined-stream@^1.0.6, combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commerce-spree@^0.0.30: - version "0.0.30" - resolved "https://registry.yarnpkg.com/commerce-spree/-/commerce-spree-0.0.30.tgz#d44248f6a109cd1bd9a7403bdd06704a1f0c2a98" - integrity sha512-NQHylP/9pwNMkl0HYfYXNNh6G7b0QXB9wwQHWrYRrgDouAF1sP3vcIAKthOba1EwCAkZnhPc/CKBstQAC0sDfw== +commerce-spree@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/commerce-spree/-/commerce-spree-0.1.0.tgz#7986bf8cdfb1fd1fbf58dcc7788b65c17e27b4ab" + integrity sha512-Yu1dt5E7jo56+ycEe5Bw1wvarGH1JbsdJKkQRm2gijdz1gQZ+3LTeEncNzwi4ZzCFyIQJeyNweEOsvgykGI/sQ== dependencies: "@plasmicpkgs/commerce" "0.0.205" "@spree/storefront-api-v2-sdk" "^5.1.1"