Spree Commerce Provider (#484)
* Include @spree/storefront-api-v2-sdk * Add basic Spree framework structure * Add Spree as allowed Framework * Fetch product images, standardize API fetch using Spree SDK * Include slug and path in products * Fetch single product during build time * PLP with searching by category * Fetch Spree Categories and Brands * Sort PLP * Search products by name * Fix option values collection * Fix hasNonMasterVariants * Sort Categories and Brands * Add configuration to show product options when there's one variant available * Enable text search for the Spree Framework * Allow removing line items * Allow updating line item quantity * Add __typename to variant options to allow adding the selected variant to the cart * Use fetch and Request from node-fetch in Spree SDK * Update Spree SDK fetcher * Show placeholder message for /chechout and adjust api fetcher type * Use kebab case instead of camel case * Remove outdated comments * Remove outdated comment * Resolve isColorProductOption duplication * Type Spree variants and line items and temporarily remove height, width and depth * Remove outdated comment * Update comments about cart discounts * Remove 'spree' prefix from isomorphicConfig and add lastUpdatedProductsPrerenderCount * Implement getAllProductPaths to prerender some products during build time * Adjust fetchers to the latest Spree SDK interface * Add types to Spree taxons mapping * Revert port change in package.json scripts * Add basic README describing Spree installation * Expand README's installation section * Upgrade Spree SDK to 4.7.0 and add node-fetch to dependencies * Order providers alphanumerically Co-authored-by: Damian Legawiec <damian@sparksolutions.co> * Sort products by available_on when using the Trending sorting in useSearch * Change the default Spree port to 4000 and update README in sync with Spree Starter changes * Save primary variant's SKU when normalizing a product from Spree * Create a new cart if Spree can't find the current using a token * Add separator to README * Add missing Error subclass * Allow placeholder images for products and line items without images * Add image * Reset tsconfig.json paths to originla values * Search taxonomies by permalinks instead of IDs * Upgrade Spree SDK to version 4.7.1 * Remove references to @framework and use relative paths instead * Generalize TypeScript and add typings to getPage * Update fetcher to avoid parsing non-JSON responses * Use original product image by default instead of resized * Link to an online demo of the Spree integration in the README * Flatten fetcher responses * Include Spree in the list of supported ecommerce backends in README * Update README.md * Format Spree's README * Add link to the Spree demo site in the main README * Update README.md * Update README.md * Allow setting a taxon id for getAllProducts * Use Spree SDK's JSON:API helpers * Sort getAllProducts by -updated_at when using a taxonomy * Remove slash '/' from line item's paths * Allow filtering variant images by option type * Upgrade checkout behavior in line with core NextJS Commerce changes * Remove dummy submitCheckout function * [NX-24] Display PDP option types sorted by position from Spree * Supply Spree primary variant if a product has no option variants * Do not throw an error if a product doesn't have NEXT_PUBLIC_SPREE_IMAGES_OPTION_FILTER * [NX-43] Uses image transformations when fetching products images * Use bind to properly call Spree SDK methods and update SDK fetcher in line with SDK 4.12.0 * Fix ESLint issues in useHook * Support account sign up, login and logout Also - Converts the guest cart to a persisted cart tied to the logged in user after log in. - Fixes issues with use-remove-item. The cart will now properly refresh after an item is removed. - Uses the logged in user's token to adjust the cart and make other authenticated requests. - Transparently refreshed the access token of the logged in user with a refresh token. Replays requests to Spree which fail with a 401 error after refreshing the access token. * Fetch logged in user's cart after login or signup but associate guest cart only after signup * Support Spree default wishlist show, add and remove wished items operations * Fetch Spree CMS Pages * Fix login, handle critical token errors and fix WishlistCard Fix to WishlistCard changes its props to be consistent with WishlistButton when calling useRemoveItem * Fix variable name (#574) Variable name should be `ChevronRight` * Update get-cart.ts (#474) include digital items Co-authored-by: Gonzalo Pozzo <gonzalo.pozzo4@gmail.com> * Update normalize.ts (#475) add missing options property to `normalizeLineItem` Co-authored-by: Gonzalo Pozzo <gonzalo.pozzo4@gmail.com> * Update add-item.ts (#473) * Update add-item.ts include digital items * Update add-item.ts include digital items Co-authored-by: Gonzalo Pozzo <gonzalo.pozzo4@gmail.com> * fix typo (#572) Co-authored-by: Gonzalo Pozzo <gonzalo.pozzo4@gmail.com> * Fix authentication.refreshToken arguments * Remove redundant comments and logs * Fix createEmptyCart request to Spree and add option to disable auto login * Fix formatting issues * Apply image transformation when fetching images for products in cart * Replace call to qs with Spree SDK built-in helper * Upgrade Spree SDK to 5.0.1 * Rename zeitFetch import to vercelFetch * Abstract fetcher JSON Content-Type checking into separate function * Rename imageUrl to url getMediaGallery already provides context for the constant * Remove return type for getProductPath The return type can be trivially determined from the returned value. * Change URL to Spree demo store in root README Co-authored-by: Gonzalo Pozzo <gonzalo.pozzo4@gmail.com> * Change label for link to Spree demo store in Spree's README Co-authored-by: Gonzalo Pozzo <gonzalo.pozzo4@gmail.com> * Change URL to Spree demo store in Spree's README Co-authored-by: Gonzalo Pozzo <gonzalo.pozzo4@gmail.com> * Use only relative paths to /framework/spree from itself Co-authored-by: tniezg <tomek.niezgoda@mail.com> Co-authored-by: Damian Legawiec <damian@sparksolutions.co> Co-authored-by: Robert Nowakowski <aplegatt@gmail.com> Co-authored-by: Grey <57859708+greyhere@users.noreply.github.com> Co-authored-by: pfcodes <phil@auroradigit.al> Co-authored-by: Gonzalo Pozzo <gonzalo.pozzo4@gmail.com> Co-authored-by: Konrad Kruk <github@konradk.pl>
This commit is contained in:
		@@ -1,4 +1,4 @@
 | 
				
			|||||||
# Available providers: local, bigcommerce, shopify, swell, saleor
 | 
					# Available providers: local, bigcommerce, shopify, swell, saleor, spree
 | 
				
			||||||
COMMERCE_PROVIDER=
 | 
					COMMERCE_PROVIDER=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BIGCOMMERCE_STOREFRONT_API_URL=
 | 
					BIGCOMMERCE_STOREFRONT_API_URL=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)
 | 
				
			|||||||
- Vendure Demo: https://vendure.vercel.store
 | 
					- Vendure Demo: https://vendure.vercel.store
 | 
				
			||||||
- Saleor Demo: https://saleor.vercel.store/
 | 
					- Saleor Demo: https://saleor.vercel.store/
 | 
				
			||||||
- Ordercloud Demo: https://ordercloud.vercel.store/
 | 
					- Ordercloud Demo: https://ordercloud.vercel.store/
 | 
				
			||||||
 | 
					- Spree Demo: https://spree.vercel.store/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Features
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,7 +29,7 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Integrations
 | 
					## Integrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Next.js Commerce integrates out-of-the-box with BigCommerce, Shopify, Swell, Saleor and Vendure. We plan to support all major ecommerce backends.
 | 
					Next.js Commerce integrates out-of-the-box with BigCommerce, Shopify, Swell, Saleor, Vendure and Spree. We plan to support all major ecommerce backends.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Considerations
 | 
					## Considerations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ const PROVIDERS = [
 | 
				
			|||||||
  'swell',
 | 
					  'swell',
 | 
				
			||||||
  'vendure',
 | 
					  'vendure',
 | 
				
			||||||
  'ordercloud',
 | 
					  'ordercloud',
 | 
				
			||||||
 | 
					  'spree',
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getProviderName() {
 | 
					function getProviderName() {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								framework/spree/.env.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								framework/spree/.env.template
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					# Template to be used for creating .env* files (.env, .env.local etc.) in the project's root directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					COMMERCE_PROVIDER=spree
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{# - NEXT_PUBLIC_* are exposed to the web browser and the server #}
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_API_HOST=http://localhost:4000
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_DEFAULT_LOCALE=en-us
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_CART_COOKIE_NAME=spree_cart_token
 | 
				
			||||||
 | 
					{# -- cookie expire in days #}
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_CART_COOKIE_EXPIRE=7
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_USER_COOKIE_NAME=spree_user_token
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_USER_COOKIE_EXPIRE=7
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_IMAGE_HOST=http://localhost:4000
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_ALLOWED_IMAGE_DOMAIN=localhost
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_PERMALINK=categories
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_PERMALINK=brands
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_ALL_PRODUCTS_TAXONOMY_ID=false
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_SHOW_SINGLE_VARIANT_OPTIONS=false
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_LAST_UPDATED_PRODUCTS_PRERENDER_COUNT=10
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_PRODUCT_PLACEHOLDER_IMAGE_URL=/product-img-placeholder.svg
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_LINE_ITEM_PLACEHOLDER_IMAGE_URL=/product-img-placeholder.svg
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_IMAGES_OPTION_FILTER=false
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_IMAGES_SIZE=1000x1000
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_IMAGES_QUALITY=100
 | 
				
			||||||
 | 
					NEXT_PUBLIC_SPREE_LOGIN_AFTER_SIGNUP=true
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								framework/spree/README-assets/screenshots.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								framework/spree/README-assets/screenshots.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 114 KiB  | 
							
								
								
									
										33
									
								
								framework/spree/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								framework/spree/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					# [Spree Commerce][1] Provider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					![Screenshots of Spree Commerce and NextJS Commerce][5]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					An integration of [Spree Commerce](https://spreecommerce.org/) within NextJS Commerce. It supports browsing and searching Spree products and adding products to the cart.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Demo**: [https://spree.vercel.store/][6]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Setup Spree - [follow the Getting Started guide](https://dev-docs.spreecommerce.org/getting-started/installation).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Setup Nextjs Commerce - [instructions for setting up NextJS Commerce][2].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Copy the `.env.template` file in this directory (`/framework/spree`) to `.env.local` in the main directory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   ```bash
 | 
				
			||||||
 | 
					   cp framework/spree/.env.template .env.local
 | 
				
			||||||
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Set `NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_PERMALINK` and `NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_PERMALINK` environment variables:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   - They rely on [taxonomies'](https://dev-docs.spreecommerce.org/internals/products#taxons-and-taxonomies) permalinks in Spree.
 | 
				
			||||||
 | 
					   - Go to the Spree admin panel and create `Categories` and `Brands` taxonomies if they don't exist and copy their permalinks into `.env.local` in NextJS Commerce.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. Finally, run `yarn dev` :tada:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[1]: https://spreecommerce.org/
 | 
				
			||||||
 | 
					[2]: https://github.com/vercel/commerce
 | 
				
			||||||
 | 
					[3]: https://github.com/spree/spree_starter
 | 
				
			||||||
 | 
					[4]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
 | 
				
			||||||
 | 
					[5]: ./README-assets/screenshots.png
 | 
				
			||||||
 | 
					[6]: https://spree.vercel.store/
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/cart/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/cart/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/catalog/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/catalog/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/catalog/products.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/catalog/products.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										44
									
								
								framework/spree/api/endpoints/checkout/get-checkout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								framework/spree/api/endpoints/checkout/get-checkout.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import type { CheckoutEndpoint } from '.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
 | 
				
			||||||
 | 
					  req: _request,
 | 
				
			||||||
 | 
					  res: response,
 | 
				
			||||||
 | 
					  config: _config,
 | 
				
			||||||
 | 
					}) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const html = `
 | 
				
			||||||
 | 
					      <!DOCTYPE html>
 | 
				
			||||||
 | 
					        <html lang="en">
 | 
				
			||||||
 | 
					        <head>
 | 
				
			||||||
 | 
					          <meta charset="UTF-8">
 | 
				
			||||||
 | 
					          <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					          <title>Checkout</title>
 | 
				
			||||||
 | 
					        </head>
 | 
				
			||||||
 | 
					        <body>
 | 
				
			||||||
 | 
					          <div style='margin: 10rem auto; text-align: center; font-family: SansSerif, "Segoe UI", Helvetica; color: #888;'>
 | 
				
			||||||
 | 
					             <svg xmlns="http://www.w3.org/2000/svg" style='height: 60px; width: 60px;' fill="none" viewBox="0 0 24 24" stroke="currentColor">
 | 
				
			||||||
 | 
					               <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
 | 
				
			||||||
 | 
					             </svg>
 | 
				
			||||||
 | 
					             <h1>Checkout not yet implemented :(</h1>
 | 
				
			||||||
 | 
					             <p>
 | 
				
			||||||
 | 
					             See <a href='https://github.com/vercel/commerce/issues/64' target='_blank'>#64</a>
 | 
				
			||||||
 | 
					             </p>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </body>
 | 
				
			||||||
 | 
					      </html>
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    response.status(200)
 | 
				
			||||||
 | 
					    response.setHeader('Content-Type', 'text/html')
 | 
				
			||||||
 | 
					    response.write(html)
 | 
				
			||||||
 | 
					    response.end()
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.error(error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const message = 'An unexpected error ocurred'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    response.status(500).json({ data: null, errors: [{ message }] })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default getCheckout
 | 
				
			||||||
							
								
								
									
										22
									
								
								framework/spree/api/endpoints/checkout/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								framework/spree/api/endpoints/checkout/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import { createEndpoint } from '@commerce/api'
 | 
				
			||||||
 | 
					import type { GetAPISchema, CommerceAPI } from '@commerce/api'
 | 
				
			||||||
 | 
					import checkoutEndpoint from '@commerce/api/endpoints/checkout'
 | 
				
			||||||
 | 
					import type { CheckoutSchema } from '@commerce/types/checkout'
 | 
				
			||||||
 | 
					import getCheckout from './get-checkout'
 | 
				
			||||||
 | 
					import type { SpreeApiProvider } from '../..'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type CheckoutAPI = GetAPISchema<
 | 
				
			||||||
 | 
					  CommerceAPI<SpreeApiProvider>,
 | 
				
			||||||
 | 
					  CheckoutSchema
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type CheckoutEndpoint = CheckoutAPI['endpoint']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handlers: CheckoutEndpoint['handlers'] = { getCheckout }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkoutApi = createEndpoint<CheckoutAPI>({
 | 
				
			||||||
 | 
					  handler: checkoutEndpoint,
 | 
				
			||||||
 | 
					  handlers,
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default checkoutApi
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/customer/address.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/customer/address.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/customer/card.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/customer/card.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/customer/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/customer/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/login/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/login/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/logout/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/logout/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/signup/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/signup/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/api/endpoints/wishlist/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/api/endpoints/wishlist/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default function noopApi(...args: any[]): void {}
 | 
				
			||||||
							
								
								
									
										45
									
								
								framework/spree/api/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								framework/spree/api/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
 | 
				
			||||||
 | 
					import { getCommerceApi as commerceApi } from '@commerce/api'
 | 
				
			||||||
 | 
					import createApiFetch from './utils/create-api-fetch'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import getAllPages from './operations/get-all-pages'
 | 
				
			||||||
 | 
					import getPage from './operations/get-page'
 | 
				
			||||||
 | 
					import getSiteInfo from './operations/get-site-info'
 | 
				
			||||||
 | 
					import getCustomerWishlist from './operations/get-customer-wishlist'
 | 
				
			||||||
 | 
					import getAllProductPaths from './operations/get-all-product-paths'
 | 
				
			||||||
 | 
					import getAllProducts from './operations/get-all-products'
 | 
				
			||||||
 | 
					import getProduct from './operations/get-product'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SpreeApiConfig extends CommerceAPIConfig {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const config: SpreeApiConfig = {
 | 
				
			||||||
 | 
					  commerceUrl: '',
 | 
				
			||||||
 | 
					  apiToken: '',
 | 
				
			||||||
 | 
					  cartCookie: '',
 | 
				
			||||||
 | 
					  customerCookie: '',
 | 
				
			||||||
 | 
					  cartCookieMaxAge: 2592000,
 | 
				
			||||||
 | 
					  fetch: createApiFetch(() => getCommerceApi().getConfig()),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const operations = {
 | 
				
			||||||
 | 
					  getAllPages,
 | 
				
			||||||
 | 
					  getPage,
 | 
				
			||||||
 | 
					  getSiteInfo,
 | 
				
			||||||
 | 
					  getCustomerWishlist,
 | 
				
			||||||
 | 
					  getAllProductPaths,
 | 
				
			||||||
 | 
					  getAllProducts,
 | 
				
			||||||
 | 
					  getProduct,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const provider = { config, operations }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeApiProvider = typeof provider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeApi<P extends SpreeApiProvider = SpreeApiProvider> =
 | 
				
			||||||
 | 
					  CommerceAPI<P>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getCommerceApi<P extends SpreeApiProvider>(
 | 
				
			||||||
 | 
					  customProvider: P = provider as any
 | 
				
			||||||
 | 
					): SpreeApi<P> {
 | 
				
			||||||
 | 
					  return commerceApi(customProvider)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										82
									
								
								framework/spree/api/operations/get-all-pages.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								framework/spree/api/operations/get-all-pages.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  OperationContext,
 | 
				
			||||||
 | 
					  OperationOptions,
 | 
				
			||||||
 | 
					} from '@commerce/api/operations'
 | 
				
			||||||
 | 
					import type { GetAllPagesOperation, Page } from '@commerce/types/page'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					import normalizePage from '../../utils/normalizations/normalize-page'
 | 
				
			||||||
 | 
					import type { IPages } from '@spree/storefront-api-v2-sdk/types/interfaces/Page'
 | 
				
			||||||
 | 
					import type { SpreeSdkVariables } from '../../types'
 | 
				
			||||||
 | 
					import type { SpreeApiConfig, SpreeApiProvider } from '../index'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function getAllPagesOperation({
 | 
				
			||||||
 | 
					  commerce,
 | 
				
			||||||
 | 
					}: OperationContext<SpreeApiProvider>) {
 | 
				
			||||||
 | 
					  async function getAllPages<T extends GetAllPagesOperation>(options?: {
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					  }): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getAllPages<T extends GetAllPagesOperation>(
 | 
				
			||||||
 | 
					    opts: {
 | 
				
			||||||
 | 
					      config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					      preview?: boolean
 | 
				
			||||||
 | 
					    } & OperationOptions
 | 
				
			||||||
 | 
					  ): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getAllPages<T extends GetAllPagesOperation>({
 | 
				
			||||||
 | 
					    config: userConfig,
 | 
				
			||||||
 | 
					    preview,
 | 
				
			||||||
 | 
					    query,
 | 
				
			||||||
 | 
					    url,
 | 
				
			||||||
 | 
					  }: {
 | 
				
			||||||
 | 
					    url?: string
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					    query?: string
 | 
				
			||||||
 | 
					  } = {}): Promise<T['data']> {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'getAllPages called. Configuration: ',
 | 
				
			||||||
 | 
					      'query: ',
 | 
				
			||||||
 | 
					      query,
 | 
				
			||||||
 | 
					      'userConfig: ',
 | 
				
			||||||
 | 
					      userConfig,
 | 
				
			||||||
 | 
					      'preview: ',
 | 
				
			||||||
 | 
					      preview,
 | 
				
			||||||
 | 
					      'url: ',
 | 
				
			||||||
 | 
					      url
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const config = commerce.getConfig(userConfig)
 | 
				
			||||||
 | 
					    const { fetch: apiFetch } = config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const variables: SpreeSdkVariables = {
 | 
				
			||||||
 | 
					      methodPath: 'pages.list',
 | 
				
			||||||
 | 
					      arguments: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          per_page: 500,
 | 
				
			||||||
 | 
					          filter: {
 | 
				
			||||||
 | 
					            locale_eq:
 | 
				
			||||||
 | 
					              config.locale || (requireConfigValue('defaultLocale') as string),
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data: spreeSuccessResponse } = await apiFetch<
 | 
				
			||||||
 | 
					      IPages,
 | 
				
			||||||
 | 
					      SpreeSdkVariables
 | 
				
			||||||
 | 
					    >('__UNUSED__', {
 | 
				
			||||||
 | 
					      variables,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const normalizedPages: Page[] = spreeSuccessResponse.data.map<Page>(
 | 
				
			||||||
 | 
					      (spreePage) =>
 | 
				
			||||||
 | 
					        normalizePage(spreeSuccessResponse, spreePage, config.locales || [])
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { pages: normalizedPages }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return getAllPages
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										97
									
								
								framework/spree/api/operations/get-all-product-paths.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								framework/spree/api/operations/get-all-product-paths.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  OperationContext,
 | 
				
			||||||
 | 
					  OperationOptions,
 | 
				
			||||||
 | 
					} from '@commerce/api/operations'
 | 
				
			||||||
 | 
					import type { Product } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type { GetAllProductPathsOperation } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					import type { IProductsSlugs, SpreeSdkVariables } from '../../types'
 | 
				
			||||||
 | 
					import getProductPath from '../../utils/get-product-path'
 | 
				
			||||||
 | 
					import type { SpreeApiConfig, SpreeApiProvider } from '..'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imagesSize = requireConfigValue('imagesSize') as string
 | 
				
			||||||
 | 
					const imagesQuality = requireConfigValue('imagesQuality') as number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function getAllProductPathsOperation({
 | 
				
			||||||
 | 
					  commerce,
 | 
				
			||||||
 | 
					}: OperationContext<SpreeApiProvider>) {
 | 
				
			||||||
 | 
					  async function getAllProductPaths<
 | 
				
			||||||
 | 
					    T extends GetAllProductPathsOperation
 | 
				
			||||||
 | 
					  >(opts?: {
 | 
				
			||||||
 | 
					    variables?: T['variables']
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					  }): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getAllProductPaths<T extends GetAllProductPathsOperation>(
 | 
				
			||||||
 | 
					    opts: {
 | 
				
			||||||
 | 
					      variables?: T['variables']
 | 
				
			||||||
 | 
					      config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    } & OperationOptions
 | 
				
			||||||
 | 
					  ): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getAllProductPaths<T extends GetAllProductPathsOperation>({
 | 
				
			||||||
 | 
					    query,
 | 
				
			||||||
 | 
					    variables: getAllProductPathsVariables = {},
 | 
				
			||||||
 | 
					    config: userConfig,
 | 
				
			||||||
 | 
					  }: {
 | 
				
			||||||
 | 
					    query?: string
 | 
				
			||||||
 | 
					    variables?: T['variables']
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					  } = {}): Promise<T['data']> {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'getAllProductPaths called. Configuration: ',
 | 
				
			||||||
 | 
					      'query: ',
 | 
				
			||||||
 | 
					      query,
 | 
				
			||||||
 | 
					      'getAllProductPathsVariables: ',
 | 
				
			||||||
 | 
					      getAllProductPathsVariables,
 | 
				
			||||||
 | 
					      'config: ',
 | 
				
			||||||
 | 
					      userConfig
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const productsCount = requireConfigValue(
 | 
				
			||||||
 | 
					      'lastUpdatedProductsPrerenderCount'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (productsCount === 0) {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        products: [],
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const variables: SpreeSdkVariables = {
 | 
				
			||||||
 | 
					      methodPath: 'products.list',
 | 
				
			||||||
 | 
					      arguments: [
 | 
				
			||||||
 | 
					        {},
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          fields: {
 | 
				
			||||||
 | 
					            product: 'slug',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          per_page: productsCount,
 | 
				
			||||||
 | 
					          image_transformation: {
 | 
				
			||||||
 | 
					            quality: imagesQuality,
 | 
				
			||||||
 | 
					            size: imagesSize,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const config = commerce.getConfig(userConfig)
 | 
				
			||||||
 | 
					    const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data: spreeSuccessResponse } = await apiFetch<
 | 
				
			||||||
 | 
					      IProductsSlugs,
 | 
				
			||||||
 | 
					      SpreeSdkVariables
 | 
				
			||||||
 | 
					    >('__UNUSED__', {
 | 
				
			||||||
 | 
					      variables,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const normalizedProductsPaths: Pick<Product, 'path'>[] =
 | 
				
			||||||
 | 
					      spreeSuccessResponse.data.map((spreeProduct) => ({
 | 
				
			||||||
 | 
					        path: getProductPath(spreeProduct),
 | 
				
			||||||
 | 
					      }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { products: normalizedProductsPaths }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return getAllProductPaths
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										92
									
								
								framework/spree/api/operations/get-all-products.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								framework/spree/api/operations/get-all-products.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					import type { Product } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type { GetAllProductsOperation } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  OperationContext,
 | 
				
			||||||
 | 
					  OperationOptions,
 | 
				
			||||||
 | 
					} from '@commerce/api/operations'
 | 
				
			||||||
 | 
					import type { IProducts } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
 | 
				
			||||||
 | 
					import type { SpreeApiConfig, SpreeApiProvider } from '../index'
 | 
				
			||||||
 | 
					import type { SpreeSdkVariables } from '../../types'
 | 
				
			||||||
 | 
					import normalizeProduct from '../../utils/normalizations/normalize-product'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imagesSize = requireConfigValue('imagesSize') as string
 | 
				
			||||||
 | 
					const imagesQuality = requireConfigValue('imagesQuality') as number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function getAllProductsOperation({
 | 
				
			||||||
 | 
					  commerce,
 | 
				
			||||||
 | 
					}: OperationContext<SpreeApiProvider>) {
 | 
				
			||||||
 | 
					  async function getAllProducts<T extends GetAllProductsOperation>(opts?: {
 | 
				
			||||||
 | 
					    variables?: T['variables']
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					  }): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getAllProducts<T extends GetAllProductsOperation>(
 | 
				
			||||||
 | 
					    opts: {
 | 
				
			||||||
 | 
					      variables?: T['variables']
 | 
				
			||||||
 | 
					      config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					      preview?: boolean
 | 
				
			||||||
 | 
					    } & OperationOptions
 | 
				
			||||||
 | 
					  ): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getAllProducts<T extends GetAllProductsOperation>({
 | 
				
			||||||
 | 
					    variables: getAllProductsVariables = {},
 | 
				
			||||||
 | 
					    config: userConfig,
 | 
				
			||||||
 | 
					  }: {
 | 
				
			||||||
 | 
					    variables?: T['variables']
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					  } = {}): Promise<{ products: Product[] }> {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'getAllProducts called. Configuration: ',
 | 
				
			||||||
 | 
					      'getAllProductsVariables: ',
 | 
				
			||||||
 | 
					      getAllProductsVariables,
 | 
				
			||||||
 | 
					      'config: ',
 | 
				
			||||||
 | 
					      userConfig
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const defaultProductsTaxonomyId = requireConfigValue(
 | 
				
			||||||
 | 
					      'allProductsTaxonomyId'
 | 
				
			||||||
 | 
					    ) as string | false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const first = getAllProductsVariables.first
 | 
				
			||||||
 | 
					    const filter = !defaultProductsTaxonomyId
 | 
				
			||||||
 | 
					      ? {}
 | 
				
			||||||
 | 
					      : { filter: { taxons: defaultProductsTaxonomyId }, sort: '-updated_at' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const variables: SpreeSdkVariables = {
 | 
				
			||||||
 | 
					      methodPath: 'products.list',
 | 
				
			||||||
 | 
					      arguments: [
 | 
				
			||||||
 | 
					        {},
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          include:
 | 
				
			||||||
 | 
					            'primary_variant,variants,images,option_types,variants.option_values',
 | 
				
			||||||
 | 
					          per_page: first,
 | 
				
			||||||
 | 
					          ...filter,
 | 
				
			||||||
 | 
					          image_transformation: {
 | 
				
			||||||
 | 
					            quality: imagesQuality,
 | 
				
			||||||
 | 
					            size: imagesSize,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const config = commerce.getConfig(userConfig)
 | 
				
			||||||
 | 
					    const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data: spreeSuccessResponse } = await apiFetch<
 | 
				
			||||||
 | 
					      IProducts,
 | 
				
			||||||
 | 
					      SpreeSdkVariables
 | 
				
			||||||
 | 
					    >('__UNUSED__', {
 | 
				
			||||||
 | 
					      variables,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const normalizedProducts: Product[] = spreeSuccessResponse.data.map(
 | 
				
			||||||
 | 
					      (spreeProduct) => normalizeProduct(spreeSuccessResponse, spreeProduct)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { products: normalizedProducts }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return getAllProducts
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								framework/spree/api/operations/get-customer-wishlist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								framework/spree/api/operations/get-customer-wishlist.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					export default function getCustomerWishlistOperation() {
 | 
				
			||||||
 | 
					  function getCustomerWishlist(): any {
 | 
				
			||||||
 | 
					    return { wishlist: {} }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return getCustomerWishlist
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										81
									
								
								framework/spree/api/operations/get-page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								framework/spree/api/operations/get-page.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  OperationContext,
 | 
				
			||||||
 | 
					  OperationOptions,
 | 
				
			||||||
 | 
					} from '@commerce/api/operations'
 | 
				
			||||||
 | 
					import type { GetPageOperation } from '@commerce/types/page'
 | 
				
			||||||
 | 
					import type { SpreeSdkVariables } from '../../types'
 | 
				
			||||||
 | 
					import type { SpreeApiConfig, SpreeApiProvider } from '..'
 | 
				
			||||||
 | 
					import type { IPage } from '@spree/storefront-api-v2-sdk/types/interfaces/Page'
 | 
				
			||||||
 | 
					import normalizePage from '../../utils/normalizations/normalize-page'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Page = any
 | 
				
			||||||
 | 
					export type GetPageResult = { page?: Page }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type PageVariables = {
 | 
				
			||||||
 | 
					  id: number
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function getPageOperation({
 | 
				
			||||||
 | 
					  commerce,
 | 
				
			||||||
 | 
					}: OperationContext<SpreeApiProvider>) {
 | 
				
			||||||
 | 
					  async function getPage<T extends GetPageOperation>(opts: {
 | 
				
			||||||
 | 
					    variables: T['variables']
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					  }): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getPage<T extends GetPageOperation>(
 | 
				
			||||||
 | 
					    opts: {
 | 
				
			||||||
 | 
					      variables: T['variables']
 | 
				
			||||||
 | 
					      config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					      preview?: boolean
 | 
				
			||||||
 | 
					    } & OperationOptions
 | 
				
			||||||
 | 
					  ): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getPage<T extends GetPageOperation>({
 | 
				
			||||||
 | 
					    url,
 | 
				
			||||||
 | 
					    config: userConfig,
 | 
				
			||||||
 | 
					    preview,
 | 
				
			||||||
 | 
					    variables: getPageVariables,
 | 
				
			||||||
 | 
					  }: {
 | 
				
			||||||
 | 
					    url?: string
 | 
				
			||||||
 | 
					    variables: T['variables']
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					  }): Promise<T['data']> {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'getPage called. Configuration: ',
 | 
				
			||||||
 | 
					      'userConfig: ',
 | 
				
			||||||
 | 
					      userConfig,
 | 
				
			||||||
 | 
					      'preview: ',
 | 
				
			||||||
 | 
					      preview,
 | 
				
			||||||
 | 
					      'url: ',
 | 
				
			||||||
 | 
					      url
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const config = commerce.getConfig(userConfig)
 | 
				
			||||||
 | 
					    const { fetch: apiFetch } = config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const variables: SpreeSdkVariables = {
 | 
				
			||||||
 | 
					      methodPath: 'pages.show',
 | 
				
			||||||
 | 
					      arguments: [getPageVariables.id],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data: spreeSuccessResponse } = await apiFetch<
 | 
				
			||||||
 | 
					      IPage,
 | 
				
			||||||
 | 
					      SpreeSdkVariables
 | 
				
			||||||
 | 
					    >('__UNUSED__', {
 | 
				
			||||||
 | 
					      variables,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const normalizedPage: Page = normalizePage(
 | 
				
			||||||
 | 
					      spreeSuccessResponse,
 | 
				
			||||||
 | 
					      spreeSuccessResponse.data,
 | 
				
			||||||
 | 
					      config.locales || []
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { page: normalizedPage }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return getPage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								framework/spree/api/operations/get-product.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								framework/spree/api/operations/get-product.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					import type { SpreeApiConfig, SpreeApiProvider } from '../index'
 | 
				
			||||||
 | 
					import type { GetProductOperation } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  OperationContext,
 | 
				
			||||||
 | 
					  OperationOptions,
 | 
				
			||||||
 | 
					} from '@commerce/api/operations'
 | 
				
			||||||
 | 
					import type { IProduct } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
 | 
				
			||||||
 | 
					import type { SpreeSdkVariables } from '../../types'
 | 
				
			||||||
 | 
					import MissingSlugVariableError from '../../errors/MissingSlugVariableError'
 | 
				
			||||||
 | 
					import normalizeProduct from '../../utils/normalizations/normalize-product'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imagesSize = requireConfigValue('imagesSize') as string
 | 
				
			||||||
 | 
					const imagesQuality = requireConfigValue('imagesQuality') as number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function getProductOperation({
 | 
				
			||||||
 | 
					  commerce,
 | 
				
			||||||
 | 
					}: OperationContext<SpreeApiProvider>) {
 | 
				
			||||||
 | 
					  async function getProduct<T extends GetProductOperation>(opts: {
 | 
				
			||||||
 | 
					    variables: T['variables']
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					  }): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getProduct<T extends GetProductOperation>(
 | 
				
			||||||
 | 
					    opts: {
 | 
				
			||||||
 | 
					      variables: T['variables']
 | 
				
			||||||
 | 
					      config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					      preview?: boolean
 | 
				
			||||||
 | 
					    } & OperationOptions
 | 
				
			||||||
 | 
					  ): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getProduct<T extends GetProductOperation>({
 | 
				
			||||||
 | 
					    query = '',
 | 
				
			||||||
 | 
					    variables: getProductVariables,
 | 
				
			||||||
 | 
					    config: userConfig,
 | 
				
			||||||
 | 
					  }: {
 | 
				
			||||||
 | 
					    query?: string
 | 
				
			||||||
 | 
					    variables?: T['variables']
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					  }): Promise<T['data']> {
 | 
				
			||||||
 | 
					    console.log(
 | 
				
			||||||
 | 
					      'getProduct called. Configuration: ',
 | 
				
			||||||
 | 
					      'getProductVariables: ',
 | 
				
			||||||
 | 
					      getProductVariables,
 | 
				
			||||||
 | 
					      'config: ',
 | 
				
			||||||
 | 
					      userConfig
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!getProductVariables?.slug) {
 | 
				
			||||||
 | 
					      throw new MissingSlugVariableError()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const variables: SpreeSdkVariables = {
 | 
				
			||||||
 | 
					      methodPath: 'products.show',
 | 
				
			||||||
 | 
					      arguments: [
 | 
				
			||||||
 | 
					        getProductVariables.slug,
 | 
				
			||||||
 | 
					        {},
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          include:
 | 
				
			||||||
 | 
					            'primary_variant,variants,images,option_types,variants.option_values',
 | 
				
			||||||
 | 
					          image_transformation: {
 | 
				
			||||||
 | 
					            quality: imagesQuality,
 | 
				
			||||||
 | 
					            size: imagesSize,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const config = commerce.getConfig(userConfig)
 | 
				
			||||||
 | 
					    const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data: spreeSuccessResponse } = await apiFetch<
 | 
				
			||||||
 | 
					      IProduct,
 | 
				
			||||||
 | 
					      SpreeSdkVariables
 | 
				
			||||||
 | 
					    >('__UNUSED__', {
 | 
				
			||||||
 | 
					      variables,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      product: normalizeProduct(
 | 
				
			||||||
 | 
					        spreeSuccessResponse,
 | 
				
			||||||
 | 
					        spreeSuccessResponse.data
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return getProduct
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										135
									
								
								framework/spree/api/operations/get-site-info.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								framework/spree/api/operations/get-site-info.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  OperationContext,
 | 
				
			||||||
 | 
					  OperationOptions,
 | 
				
			||||||
 | 
					} from '@commerce/api/operations'
 | 
				
			||||||
 | 
					import type { Category, GetSiteInfoOperation } from '@commerce/types/site'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ITaxons,
 | 
				
			||||||
 | 
					  TaxonAttr,
 | 
				
			||||||
 | 
					} from '@spree/storefront-api-v2-sdk/types/interfaces/Taxon'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					import type { SpreeSdkVariables } from '../../types'
 | 
				
			||||||
 | 
					import type { SpreeApiConfig, SpreeApiProvider } from '..'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const taxonsSort = (spreeTaxon1: TaxonAttr, spreeTaxon2: TaxonAttr): number => {
 | 
				
			||||||
 | 
					  const { left: left1, right: right1 } = spreeTaxon1.attributes
 | 
				
			||||||
 | 
					  const { left: left2, right: right2 } = spreeTaxon2.attributes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (right1 < left2) {
 | 
				
			||||||
 | 
					    return -1
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (right2 < left1) {
 | 
				
			||||||
 | 
					    return 1
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type GetSiteInfoResult<
 | 
				
			||||||
 | 
					  T extends { categories: any[]; brands: any[] } = {
 | 
				
			||||||
 | 
					    categories: Category[]
 | 
				
			||||||
 | 
					    brands: any[]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					> = T
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function getSiteInfoOperation({
 | 
				
			||||||
 | 
					  commerce,
 | 
				
			||||||
 | 
					}: OperationContext<SpreeApiProvider>) {
 | 
				
			||||||
 | 
					  async function getSiteInfo<T extends GetSiteInfoOperation>(opts?: {
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					  }): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getSiteInfo<T extends GetSiteInfoOperation>(
 | 
				
			||||||
 | 
					    opts: {
 | 
				
			||||||
 | 
					      config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					      preview?: boolean
 | 
				
			||||||
 | 
					    } & OperationOptions
 | 
				
			||||||
 | 
					  ): Promise<T['data']>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function getSiteInfo<T extends GetSiteInfoOperation>({
 | 
				
			||||||
 | 
					    query,
 | 
				
			||||||
 | 
					    variables: getSiteInfoVariables = {},
 | 
				
			||||||
 | 
					    config: userConfig,
 | 
				
			||||||
 | 
					  }: {
 | 
				
			||||||
 | 
					    query?: string
 | 
				
			||||||
 | 
					    variables?: any
 | 
				
			||||||
 | 
					    config?: Partial<SpreeApiConfig>
 | 
				
			||||||
 | 
					    preview?: boolean
 | 
				
			||||||
 | 
					  } = {}): Promise<GetSiteInfoResult> {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'getSiteInfo called. Configuration: ',
 | 
				
			||||||
 | 
					      'query: ',
 | 
				
			||||||
 | 
					      query,
 | 
				
			||||||
 | 
					      'getSiteInfoVariables ',
 | 
				
			||||||
 | 
					      getSiteInfoVariables,
 | 
				
			||||||
 | 
					      'config: ',
 | 
				
			||||||
 | 
					      userConfig
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const createVariables = (parentPermalink: string): SpreeSdkVariables => ({
 | 
				
			||||||
 | 
					      methodPath: 'taxons.list',
 | 
				
			||||||
 | 
					      arguments: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          filter: {
 | 
				
			||||||
 | 
					            parent_permalink: parentPermalink,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const config = commerce.getConfig(userConfig)
 | 
				
			||||||
 | 
					    const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data: spreeCategoriesSuccessResponse } = await apiFetch<
 | 
				
			||||||
 | 
					      ITaxons,
 | 
				
			||||||
 | 
					      SpreeSdkVariables
 | 
				
			||||||
 | 
					    >('__UNUSED__', {
 | 
				
			||||||
 | 
					      variables: createVariables(
 | 
				
			||||||
 | 
					        requireConfigValue('categoriesTaxonomyPermalink') as string
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data: spreeBrandsSuccessResponse } = await apiFetch<
 | 
				
			||||||
 | 
					      ITaxons,
 | 
				
			||||||
 | 
					      SpreeSdkVariables
 | 
				
			||||||
 | 
					    >('__UNUSED__', {
 | 
				
			||||||
 | 
					      variables: createVariables(
 | 
				
			||||||
 | 
					        requireConfigValue('brandsTaxonomyPermalink') as string
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const normalizedCategories: GetSiteInfoOperation['data']['categories'] =
 | 
				
			||||||
 | 
					      spreeCategoriesSuccessResponse.data
 | 
				
			||||||
 | 
					        .sort(taxonsSort)
 | 
				
			||||||
 | 
					        .map((spreeTaxon: TaxonAttr) => {
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            id: spreeTaxon.id,
 | 
				
			||||||
 | 
					            name: spreeTaxon.attributes.name,
 | 
				
			||||||
 | 
					            slug: spreeTaxon.id,
 | 
				
			||||||
 | 
					            path: spreeTaxon.id,
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const normalizedBrands: GetSiteInfoOperation['data']['brands'] =
 | 
				
			||||||
 | 
					      spreeBrandsSuccessResponse.data
 | 
				
			||||||
 | 
					        .sort(taxonsSort)
 | 
				
			||||||
 | 
					        .map((spreeTaxon: TaxonAttr) => {
 | 
				
			||||||
 | 
					          return {
 | 
				
			||||||
 | 
					            node: {
 | 
				
			||||||
 | 
					              entityId: spreeTaxon.id,
 | 
				
			||||||
 | 
					              path: `brands/${spreeTaxon.id}`,
 | 
				
			||||||
 | 
					              name: spreeTaxon.attributes.name,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      categories: normalizedCategories,
 | 
				
			||||||
 | 
					      brands: normalizedBrands,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return getSiteInfo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								framework/spree/api/operations/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								framework/spree/api/operations/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					export { default as getPage } from './get-page'
 | 
				
			||||||
 | 
					export { default as getSiteInfo } from './get-site-info'
 | 
				
			||||||
 | 
					export { default as getAllPages } from './get-all-pages'
 | 
				
			||||||
 | 
					export { default as getProduct } from './get-product'
 | 
				
			||||||
 | 
					export { default as getAllProducts } from './get-all-products'
 | 
				
			||||||
 | 
					export { default as getAllProductPaths } from './get-all-product-paths'
 | 
				
			||||||
							
								
								
									
										79
									
								
								framework/spree/api/utils/create-api-fetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								framework/spree/api/utils/create-api-fetch.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import { SpreeApiConfig } from '..'
 | 
				
			||||||
 | 
					import { errors, makeClient } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					import convertSpreeErrorToGraphQlError from '../../utils/convert-spree-error-to-graph-ql-error'
 | 
				
			||||||
 | 
					import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
 | 
				
			||||||
 | 
					import getSpreeSdkMethodFromEndpointPath from '../../utils/get-spree-sdk-method-from-endpoint-path'
 | 
				
			||||||
 | 
					import SpreeSdkMethodFromEndpointPathError from '../../errors/SpreeSdkMethodFromEndpointPathError'
 | 
				
			||||||
 | 
					import { GraphQLFetcher, GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import createCustomizedFetchFetcher, {
 | 
				
			||||||
 | 
					  fetchResponseKey,
 | 
				
			||||||
 | 
					} from '../../utils/create-customized-fetch-fetcher'
 | 
				
			||||||
 | 
					import fetch, { Request } from 'node-fetch'
 | 
				
			||||||
 | 
					import type { SpreeSdkResponseWithRawResponse } from '../../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type CreateApiFetch = (
 | 
				
			||||||
 | 
					  getConfig: () => SpreeApiConfig
 | 
				
			||||||
 | 
					) => GraphQLFetcher<GraphQLFetcherResult<any>, any>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: GraphQLFetcher<GraphQLFetcherResult<any>, any> should be GraphQLFetcher<GraphQLFetcherResult<any>, SpreeSdkVariables>.
 | 
				
			||||||
 | 
					// But CommerceAPIConfig['fetch'] cannot be extended from Variables = any to SpreeSdkVariables.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createApiFetch: CreateApiFetch = (_getConfig) => {
 | 
				
			||||||
 | 
					  const client = makeClient({
 | 
				
			||||||
 | 
					    host: requireConfigValue('apiHost') as string,
 | 
				
			||||||
 | 
					    createFetcher: (fetcherOptions) => {
 | 
				
			||||||
 | 
					      return createCustomizedFetchFetcher({
 | 
				
			||||||
 | 
					        fetch,
 | 
				
			||||||
 | 
					        requestConstructor: Request,
 | 
				
			||||||
 | 
					        ...fetcherOptions,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return async (url, queryData = {}, fetchOptions = {}) => {
 | 
				
			||||||
 | 
					    console.log(
 | 
				
			||||||
 | 
					      'apiFetch called. query = ',
 | 
				
			||||||
 | 
					      'url = ',
 | 
				
			||||||
 | 
					      url,
 | 
				
			||||||
 | 
					      'queryData = ',
 | 
				
			||||||
 | 
					      queryData,
 | 
				
			||||||
 | 
					      'fetchOptions = ',
 | 
				
			||||||
 | 
					      fetchOptions
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { variables } = queryData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!variables) {
 | 
				
			||||||
 | 
					      throw new SpreeSdkMethodFromEndpointPathError(
 | 
				
			||||||
 | 
					        `Required SpreeSdkVariables not provided.`
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const storeResponse: ResultResponse<SpreeSdkResponseWithRawResponse> =
 | 
				
			||||||
 | 
					      await getSpreeSdkMethodFromEndpointPath(
 | 
				
			||||||
 | 
					        client,
 | 
				
			||||||
 | 
					        variables.methodPath
 | 
				
			||||||
 | 
					      )(...variables.arguments)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (storeResponse.isSuccess()) {
 | 
				
			||||||
 | 
					      const data = storeResponse.success()
 | 
				
			||||||
 | 
					      const rawFetchResponse = data[fetchResponseKey]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        data,
 | 
				
			||||||
 | 
					        res: rawFetchResponse,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const storeResponseError = storeResponse.fail()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (storeResponseError instanceof errors.SpreeError) {
 | 
				
			||||||
 | 
					      throw convertSpreeErrorToGraphQlError(storeResponseError)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw storeResponseError
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default createApiFetch
 | 
				
			||||||
							
								
								
									
										3
									
								
								framework/spree/api/utils/fetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								framework/spree/api/utils/fetch.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					import vercelFetch from '@vercel/fetch'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default vercelFetch()
 | 
				
			||||||
							
								
								
									
										3
									
								
								framework/spree/auth/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								framework/spree/auth/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export { default as useLogin } from './use-login'
 | 
				
			||||||
 | 
					export { default as useLogout } from './use-logout'
 | 
				
			||||||
 | 
					export { default as useSignup } from './use-signup'
 | 
				
			||||||
							
								
								
									
										85
									
								
								framework/spree/auth/use-login.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								framework/spree/auth/use-login.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
 | 
					import type { MutationHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import useLogin, { UseLogin } from '@commerce/auth/use-login'
 | 
				
			||||||
 | 
					import type { LoginHook } from '@commerce/types/login'
 | 
				
			||||||
 | 
					import type { AuthTokenAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Authentication'
 | 
				
			||||||
 | 
					import { FetcherError, ValidationError } from '@commerce/utils/errors'
 | 
				
			||||||
 | 
					import useCustomer from '../customer/use-customer'
 | 
				
			||||||
 | 
					import useCart from '../cart/use-cart'
 | 
				
			||||||
 | 
					import useWishlist from '../wishlist/use-wishlist'
 | 
				
			||||||
 | 
					import login from '../utils/login'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useLogin as UseLogin<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: MutationHook<LoginHook> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'authentication',
 | 
				
			||||||
 | 
					    query: 'getToken',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useLogin fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { email, password } = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!email || !password) {
 | 
				
			||||||
 | 
					      throw new ValidationError({
 | 
				
			||||||
 | 
					        message: 'Email and password need to be provided.',
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const getTokenParameters: AuthTokenAttr = {
 | 
				
			||||||
 | 
					      username: email,
 | 
				
			||||||
 | 
					      password,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await login(fetch, getTokenParameters, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					    } catch (getTokenError) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        getTokenError instanceof FetcherError &&
 | 
				
			||||||
 | 
					        getTokenError.status === 400
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        // Change the error message to be more user friendly.
 | 
				
			||||||
 | 
					        throw new FetcherError({
 | 
				
			||||||
 | 
					          status: getTokenError.status,
 | 
				
			||||||
 | 
					          message: 'The email or password is invalid.',
 | 
				
			||||||
 | 
					          code: getTokenError.code,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      throw getTokenError
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ fetch }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<MutationHook<LoginHook>['useHook']> =
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        const customer = useCustomer()
 | 
				
			||||||
 | 
					        const cart = useCart()
 | 
				
			||||||
 | 
					        const wishlist = useWishlist()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return useCallback(
 | 
				
			||||||
 | 
					          async function login(input) {
 | 
				
			||||||
 | 
					            const data = await fetch({ input })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await customer.revalidate()
 | 
				
			||||||
 | 
					            await cart.revalidate()
 | 
				
			||||||
 | 
					            await wishlist.revalidate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return data
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          [customer, cart, wishlist]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										80
									
								
								framework/spree/auth/use-logout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								framework/spree/auth/use-logout.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					import { MutationHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import useLogout, { UseLogout } from '@commerce/auth/use-logout'
 | 
				
			||||||
 | 
					import type { LogoutHook } from '@commerce/types/logout'
 | 
				
			||||||
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
 | 
					import useCustomer from '../customer/use-customer'
 | 
				
			||||||
 | 
					import useCart from '../cart/use-cart'
 | 
				
			||||||
 | 
					import useWishlist from '../wishlist/use-wishlist'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ensureUserTokenResponse,
 | 
				
			||||||
 | 
					  removeUserTokenResponse,
 | 
				
			||||||
 | 
					} from '../utils/tokens/user-token-response'
 | 
				
			||||||
 | 
					import revokeUserTokens from '../utils/tokens/revoke-user-tokens'
 | 
				
			||||||
 | 
					import TokensNotRejectedError from '../errors/TokensNotRejectedError'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useLogout as UseLogout<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: MutationHook<LogoutHook> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'authentication',
 | 
				
			||||||
 | 
					    query: 'revokeToken',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useLogout fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const userToken = ensureUserTokenResponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (userToken) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        // Revoke any tokens associated with the logged in user.
 | 
				
			||||||
 | 
					        await revokeUserTokens(fetch, {
 | 
				
			||||||
 | 
					          accessToken: userToken.access_token,
 | 
				
			||||||
 | 
					          refreshToken: userToken.refresh_token,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      } catch (revokeUserTokenError) {
 | 
				
			||||||
 | 
					        // Squash token revocation errors and rethrow anything else.
 | 
				
			||||||
 | 
					        if (!(revokeUserTokenError instanceof TokensNotRejectedError)) {
 | 
				
			||||||
 | 
					          throw revokeUserTokenError
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Whether token revocation succeeded or not, remove them from local storage.
 | 
				
			||||||
 | 
					      removeUserTokenResponse()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ fetch }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<MutationHook<LogoutHook>['useHook']> =
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        const customer = useCustomer({
 | 
				
			||||||
 | 
					          swrOptions: { isPaused: () => true },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        const cart = useCart({
 | 
				
			||||||
 | 
					          swrOptions: { isPaused: () => true },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        const wishlist = useWishlist({
 | 
				
			||||||
 | 
					          swrOptions: { isPaused: () => true },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return useCallback(async () => {
 | 
				
			||||||
 | 
					          const data = await fetch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          await customer.mutate(null, false)
 | 
				
			||||||
 | 
					          await cart.mutate(null, false)
 | 
				
			||||||
 | 
					          await wishlist.mutate(null, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return data
 | 
				
			||||||
 | 
					        }, [customer, cart, wishlist])
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										95
									
								
								framework/spree/auth/use-signup.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								framework/spree/auth/use-signup.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import type { MutationHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import useSignup, { UseSignup } from '@commerce/auth/use-signup'
 | 
				
			||||||
 | 
					import type { SignupHook } from '@commerce/types/signup'
 | 
				
			||||||
 | 
					import { ValidationError } from '@commerce/utils/errors'
 | 
				
			||||||
 | 
					import type { IAccount } from '@spree/storefront-api-v2-sdk/types/interfaces/Account'
 | 
				
			||||||
 | 
					import type { AuthTokenAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Authentication'
 | 
				
			||||||
 | 
					import useCustomer from '../customer/use-customer'
 | 
				
			||||||
 | 
					import useCart from '../cart/use-cart'
 | 
				
			||||||
 | 
					import useWishlist from '../wishlist/use-wishlist'
 | 
				
			||||||
 | 
					import login from '../utils/login'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../isomorphic-config'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useSignup as UseSignup<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: MutationHook<SignupHook> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'account',
 | 
				
			||||||
 | 
					    query: 'create',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useSignup fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { email, password } = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!email || !password) {
 | 
				
			||||||
 | 
					      throw new ValidationError({
 | 
				
			||||||
 | 
					        message: 'Email and password need to be provided.',
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: Replace any with specific type from Spree SDK
 | 
				
			||||||
 | 
					    // once it's added to the SDK.
 | 
				
			||||||
 | 
					    const createAccountParameters: any = {
 | 
				
			||||||
 | 
					      user: {
 | 
				
			||||||
 | 
					        email,
 | 
				
			||||||
 | 
					        password,
 | 
				
			||||||
 | 
					        // The stock NJC interface doesn't have a
 | 
				
			||||||
 | 
					        // password confirmation field, so just copy password.
 | 
				
			||||||
 | 
					        passwordConfirmation: password,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create the user account.
 | 
				
			||||||
 | 
					    await fetch<GraphQLFetcherResult<IAccount>>({
 | 
				
			||||||
 | 
					      variables: {
 | 
				
			||||||
 | 
					        methodPath: 'account.create',
 | 
				
			||||||
 | 
					        arguments: [createAccountParameters],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const getTokenParameters: AuthTokenAttr = {
 | 
				
			||||||
 | 
					      username: email,
 | 
				
			||||||
 | 
					      password,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Login immediately after the account is created.
 | 
				
			||||||
 | 
					    if (requireConfigValue('loginAfterSignup')) {
 | 
				
			||||||
 | 
					      await login(fetch, getTokenParameters, true)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ fetch }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<MutationHook<SignupHook>['useHook']> =
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        const customer = useCustomer()
 | 
				
			||||||
 | 
					        const cart = useCart()
 | 
				
			||||||
 | 
					        const wishlist = useWishlist()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return useCallback(
 | 
				
			||||||
 | 
					          async (input) => {
 | 
				
			||||||
 | 
					            const data = await fetch({ input })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await customer.revalidate()
 | 
				
			||||||
 | 
					            await cart.revalidate()
 | 
				
			||||||
 | 
					            await wishlist.revalidate()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return data
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          [customer, cart, wishlist]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								framework/spree/cart/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								framework/spree/cart/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					export { default as useCart } from './use-cart'
 | 
				
			||||||
 | 
					export { default as useAddItem } from './use-add-item'
 | 
				
			||||||
 | 
					export { default as useRemoveItem } from './use-remove-item'
 | 
				
			||||||
 | 
					export { default as useUpdateItem } from './use-update-item'
 | 
				
			||||||
							
								
								
									
										117
									
								
								framework/spree/cart/use-add-item.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								framework/spree/cart/use-add-item.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					import useAddItem from '@commerce/cart/use-add-item'
 | 
				
			||||||
 | 
					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 '../utils/normalizations/normalize-cart'
 | 
				
			||||||
 | 
					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 { setCartToken } from '../utils/tokens/cart-token'
 | 
				
			||||||
 | 
					import ensureIToken from '../utils/tokens/ensure-itoken'
 | 
				
			||||||
 | 
					import createEmptyCart from '../utils/create-empty-cart'
 | 
				
			||||||
 | 
					import { FetcherError } from '@commerce/utils/errors'
 | 
				
			||||||
 | 
					import isLoggedIn from '../utils/tokens/is-logged-in'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useAddItem as UseAddItem<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: MutationHook<AddItemHook> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'cart',
 | 
				
			||||||
 | 
					    query: 'addItem',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useAddItem fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { quantity, productId, variantId } = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const safeQuantity = quantity ?? 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let token: IToken | undefined = ensureIToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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(','),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!token) {
 | 
				
			||||||
 | 
					      const { data: spreeCartCreateSuccessResponse } = await createEmptyCart(
 | 
				
			||||||
 | 
					        fetch
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setCartToken(spreeCartCreateSuccessResponse.data.attributes.token)
 | 
				
			||||||
 | 
					      token = ensureIToken()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const { data: spreeSuccessResponse } = await fetch<
 | 
				
			||||||
 | 
					        GraphQLFetcherResult<IOrder>
 | 
				
			||||||
 | 
					      >({
 | 
				
			||||||
 | 
					        variables: {
 | 
				
			||||||
 | 
					          methodPath: 'cart.addItem',
 | 
				
			||||||
 | 
					          arguments: [token, addItemParameters],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return normalizeCart(spreeSuccessResponse, spreeSuccessResponse.data)
 | 
				
			||||||
 | 
					    } catch (addItemError) {
 | 
				
			||||||
 | 
					      if (addItemError instanceof FetcherError && addItemError.status === 404) {
 | 
				
			||||||
 | 
					        const { data: spreeRetroactiveCartCreateSuccessResponse } =
 | 
				
			||||||
 | 
					          await createEmptyCart(fetch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isLoggedIn()) {
 | 
				
			||||||
 | 
					          setCartToken(
 | 
				
			||||||
 | 
					            spreeRetroactiveCartCreateSuccessResponse.data.attributes.token
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Return an empty cart. The user has to add the item again.
 | 
				
			||||||
 | 
					        // This is going to be a rare situation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return normalizeCart(
 | 
				
			||||||
 | 
					          spreeRetroactiveCartCreateSuccessResponse,
 | 
				
			||||||
 | 
					          spreeRetroactiveCartCreateSuccessResponse.data
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      throw addItemError
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ fetch }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<MutationHook<AddItemHook>['useHook']> =
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        const { mutate } = useCart()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return useCallback(
 | 
				
			||||||
 | 
					          async (input) => {
 | 
				
			||||||
 | 
					            const data = await fetch({ input })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await mutate(data, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return data
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          [mutate]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										123
									
								
								framework/spree/cart/use-cart.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								framework/spree/cart/use-cart.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					import { useMemo } from 'react'
 | 
				
			||||||
 | 
					import type { SWRHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					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 '../utils/normalizations/normalize-cart'
 | 
				
			||||||
 | 
					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 { FetcherError } from '@commerce/utils/errors'
 | 
				
			||||||
 | 
					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'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imagesSize = requireConfigValue('imagesSize') as string
 | 
				
			||||||
 | 
					const imagesQuality = requireConfigValue('imagesQuality') as number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useCart as UseCart<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'cart',
 | 
				
			||||||
 | 
					    query: 'show',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useCart fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let spreeCartResponse: IOrder | null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const token: IToken | undefined = ensureIToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!token) {
 | 
				
			||||||
 | 
					      spreeCartResponse = null
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const { data: spreeCartShowSuccessResponse } = await fetch<
 | 
				
			||||||
 | 
					          GraphQLFetcherResult<IOrder>
 | 
				
			||||||
 | 
					        >({
 | 
				
			||||||
 | 
					          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 normalizeCart(spreeCartResponse, spreeCartResponse.data)
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ useData }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<SWRHook<GetCartHook>['useHook']> = (
 | 
				
			||||||
 | 
					      input
 | 
				
			||||||
 | 
					    ) => {
 | 
				
			||||||
 | 
					      const response = useData({
 | 
				
			||||||
 | 
					        swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return useMemo<typeof response & { isEmpty: boolean }>(() => {
 | 
				
			||||||
 | 
					        return Object.create(response, {
 | 
				
			||||||
 | 
					          isEmpty: {
 | 
				
			||||||
 | 
					            get() {
 | 
				
			||||||
 | 
					              return (response.data?.lineItems.length ?? 0) === 0
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            enumerable: true,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }, [response])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										118
									
								
								framework/spree/cart/use-remove-item.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								framework/spree/cart/use-remove-item.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					import type { MutationHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import useRemoveItem from '@commerce/cart/use-remove-item'
 | 
				
			||||||
 | 
					import type { UseRemoveItem } from '@commerce/cart/use-remove-item'
 | 
				
			||||||
 | 
					import type { RemoveItemHook } from '@commerce/types/cart'
 | 
				
			||||||
 | 
					import useCart from './use-cart'
 | 
				
			||||||
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
 | 
					import normalizeCart from '../utils/normalizations/normalize-cart'
 | 
				
			||||||
 | 
					import type { IOrder } from '@spree/storefront-api-v2-sdk/types/interfaces/Order'
 | 
				
			||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import type { IQuery } from '@spree/storefront-api-v2-sdk/types/interfaces/Query'
 | 
				
			||||||
 | 
					import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
 | 
				
			||||||
 | 
					import ensureIToken from '../utils/tokens/ensure-itoken'
 | 
				
			||||||
 | 
					import createEmptyCart from '../utils/create-empty-cart'
 | 
				
			||||||
 | 
					import { setCartToken } from '../utils/tokens/cart-token'
 | 
				
			||||||
 | 
					import { FetcherError } from '@commerce/utils/errors'
 | 
				
			||||||
 | 
					import isLoggedIn from '../utils/tokens/is-logged-in'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useRemoveItem as UseRemoveItem<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: MutationHook<RemoveItemHook> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'cart',
 | 
				
			||||||
 | 
					    query: 'removeItem',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useRemoveItem fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { itemId: lineItemId } = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let token: IToken | undefined = ensureIToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!token) {
 | 
				
			||||||
 | 
					      const { data: spreeCartCreateSuccessResponse } = await createEmptyCart(
 | 
				
			||||||
 | 
					        fetch
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      setCartToken(spreeCartCreateSuccessResponse.data.attributes.token)
 | 
				
			||||||
 | 
					      token = ensureIToken()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const removeItemParameters: IQuery = {
 | 
				
			||||||
 | 
					      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(','),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const { data: spreeSuccessResponse } = await fetch<
 | 
				
			||||||
 | 
					        GraphQLFetcherResult<IOrder>
 | 
				
			||||||
 | 
					      >({
 | 
				
			||||||
 | 
					        variables: {
 | 
				
			||||||
 | 
					          methodPath: 'cart.removeItem',
 | 
				
			||||||
 | 
					          arguments: [token, lineItemId, removeItemParameters],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return normalizeCart(spreeSuccessResponse, spreeSuccessResponse.data)
 | 
				
			||||||
 | 
					    } catch (removeItemError) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        removeItemError instanceof FetcherError &&
 | 
				
			||||||
 | 
					        removeItemError.status === 404
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        const { data: spreeRetroactiveCartCreateSuccessResponse } =
 | 
				
			||||||
 | 
					          await createEmptyCart(fetch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!isLoggedIn()) {
 | 
				
			||||||
 | 
					          setCartToken(
 | 
				
			||||||
 | 
					            spreeRetroactiveCartCreateSuccessResponse.data.attributes.token
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Return an empty cart. This is going to be a rare situation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return normalizeCart(
 | 
				
			||||||
 | 
					          spreeRetroactiveCartCreateSuccessResponse,
 | 
				
			||||||
 | 
					          spreeRetroactiveCartCreateSuccessResponse.data
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      throw removeItemError
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ fetch }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<MutationHook<RemoveItemHook>['useHook']> =
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        const { mutate } = useCart()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return useCallback(
 | 
				
			||||||
 | 
					          async (input) => {
 | 
				
			||||||
 | 
					            const data = await fetch({ input: { itemId: input.id } })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Upon calling cart.removeItem, Spree returns the old version of the cart,
 | 
				
			||||||
 | 
					            // with the already removed line item. Invalidate the useCart mutation
 | 
				
			||||||
 | 
					            // to fetch the cart again.
 | 
				
			||||||
 | 
					            await mutate(data, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return data
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          [mutate]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										145
									
								
								framework/spree/cart/use-update-item.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								framework/spree/cart/use-update-item.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
				
			|||||||
 | 
					import type { MutationHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
 | 
				
			||||||
 | 
					import type { UpdateItemHook } from '@commerce/types/cart'
 | 
				
			||||||
 | 
					import useCart from './use-cart'
 | 
				
			||||||
 | 
					import { useMemo } from 'react'
 | 
				
			||||||
 | 
					import { FetcherError, ValidationError } from '@commerce/utils/errors'
 | 
				
			||||||
 | 
					import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
 | 
				
			||||||
 | 
					import type { SetQuantity } from '@spree/storefront-api-v2-sdk/types/interfaces/endpoints/CartClass'
 | 
				
			||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import type { IOrder } from '@spree/storefront-api-v2-sdk/types/interfaces/Order'
 | 
				
			||||||
 | 
					import normalizeCart from '../utils/normalizations/normalize-cart'
 | 
				
			||||||
 | 
					import debounce from 'lodash.debounce'
 | 
				
			||||||
 | 
					import ensureIToken from '../utils/tokens/ensure-itoken'
 | 
				
			||||||
 | 
					import createEmptyCart from '../utils/create-empty-cart'
 | 
				
			||||||
 | 
					import { setCartToken } from '../utils/tokens/cart-token'
 | 
				
			||||||
 | 
					import isLoggedIn from '../utils/tokens/is-logged-in'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useUpdateItem as UseUpdateItem<any>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: MutationHook<UpdateItemHook> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'cart',
 | 
				
			||||||
 | 
					    query: 'setQuantity',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useRemoveItem fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { itemId, item } = input
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!item.quantity) {
 | 
				
			||||||
 | 
					      throw new ValidationError({
 | 
				
			||||||
 | 
					        message: 'Line item quantity 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 setQuantityParameters: SetQuantity = {
 | 
				
			||||||
 | 
					        line_item_id: itemId,
 | 
				
			||||||
 | 
					        quantity: item.quantity,
 | 
				
			||||||
 | 
					        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: spreeSuccessResponse } = await fetch<
 | 
				
			||||||
 | 
					        GraphQLFetcherResult<IOrder>
 | 
				
			||||||
 | 
					      >({
 | 
				
			||||||
 | 
					        variables: {
 | 
				
			||||||
 | 
					          methodPath: 'cart.setQuantity',
 | 
				
			||||||
 | 
					          arguments: [token, setQuantityParameters],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return normalizeCart(spreeSuccessResponse, spreeSuccessResponse.data)
 | 
				
			||||||
 | 
					    } 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return normalizeCart(
 | 
				
			||||||
 | 
					          spreeRetroactiveCartCreateSuccessResponse,
 | 
				
			||||||
 | 
					          spreeRetroactiveCartCreateSuccessResponse.data
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      throw updateItemError
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ fetch }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<MutationHook<UpdateItemHook>['useHook']> =
 | 
				
			||||||
 | 
					      (context) => {
 | 
				
			||||||
 | 
					        const { mutate } = useCart()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return useMemo(
 | 
				
			||||||
 | 
					          () =>
 | 
				
			||||||
 | 
					            debounce(async (input: UpdateItemHook['actionInput']) => {
 | 
				
			||||||
 | 
					              const itemId = context?.item?.id
 | 
				
			||||||
 | 
					              const productId = input.productId ?? context?.item?.productId
 | 
				
			||||||
 | 
					              const variantId = input.variantId ?? context?.item?.variantId
 | 
				
			||||||
 | 
					              const quantity = input.quantity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              if (!itemId || !productId || !variantId) {
 | 
				
			||||||
 | 
					                throw new ValidationError({
 | 
				
			||||||
 | 
					                  message: 'Invalid input used for this operation',
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              const data = await fetch({
 | 
				
			||||||
 | 
					                input: {
 | 
				
			||||||
 | 
					                  item: {
 | 
				
			||||||
 | 
					                    productId,
 | 
				
			||||||
 | 
					                    variantId,
 | 
				
			||||||
 | 
					                    quantity,
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                  itemId,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              await mutate(data, false)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              return data
 | 
				
			||||||
 | 
					            }, context?.wait ?? 500),
 | 
				
			||||||
 | 
					          [mutate, context]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								framework/spree/checkout/use-checkout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								framework/spree/checkout/use-checkout.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					import { SWRHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import useCheckout, { UseCheckout } from '@commerce/checkout/use-checkout'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useCheckout as UseCheckout<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: SWRHook<any> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    // TODO: Revise url and query
 | 
				
			||||||
 | 
					    url: 'checkout',
 | 
				
			||||||
 | 
					    query: 'show',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {},
 | 
				
			||||||
 | 
					  useHook:
 | 
				
			||||||
 | 
					    ({ useData }) =>
 | 
				
			||||||
 | 
					    async (input) => ({}),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								framework/spree/commerce.config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								framework/spree/commerce.config.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "provider": "spree",
 | 
				
			||||||
 | 
					  "features": {
 | 
				
			||||||
 | 
					    "wishlist": true,
 | 
				
			||||||
 | 
					    "cart": true,
 | 
				
			||||||
 | 
					    "search": true,
 | 
				
			||||||
 | 
					    "customerAuth": true,
 | 
				
			||||||
 | 
					    "customCheckout": false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								framework/spree/customer/address/use-add-item.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								framework/spree/customer/address/use-add-item.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import useAddItem from '@commerce/customer/address/use-add-item'
 | 
				
			||||||
 | 
					import type { UseAddItem } from '@commerce/customer/address/use-add-item'
 | 
				
			||||||
 | 
					import type { MutationHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useAddItem as UseAddItem<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: MutationHook<any> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'account',
 | 
				
			||||||
 | 
					    query: 'createAddress',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {},
 | 
				
			||||||
 | 
					  useHook:
 | 
				
			||||||
 | 
					    ({ fetch }) =>
 | 
				
			||||||
 | 
					    () =>
 | 
				
			||||||
 | 
					    async () => ({}),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								framework/spree/customer/card/use-add-item.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								framework/spree/customer/card/use-add-item.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					import useAddItem from '@commerce/customer/address/use-add-item'
 | 
				
			||||||
 | 
					import type { UseAddItem } from '@commerce/customer/address/use-add-item'
 | 
				
			||||||
 | 
					import type { MutationHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useAddItem as UseAddItem<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: MutationHook<any> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    // TODO: Revise url and query
 | 
				
			||||||
 | 
					    url: 'checkout',
 | 
				
			||||||
 | 
					    query: 'addPayment',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {},
 | 
				
			||||||
 | 
					  useHook:
 | 
				
			||||||
 | 
					    ({ fetch }) =>
 | 
				
			||||||
 | 
					    () =>
 | 
				
			||||||
 | 
					    async () => ({}),
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/customer/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/customer/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export { default as useCustomer } from './use-customer'
 | 
				
			||||||
							
								
								
									
										83
									
								
								framework/spree/customer/use-customer.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								framework/spree/customer/use-customer.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					import type { SWRHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import useCustomer from '@commerce/customer/use-customer'
 | 
				
			||||||
 | 
					import type { UseCustomer } from '@commerce/customer/use-customer'
 | 
				
			||||||
 | 
					import type { CustomerHook } from '@commerce/types/customer'
 | 
				
			||||||
 | 
					import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
 | 
				
			||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import type { IAccount } from '@spree/storefront-api-v2-sdk/types/interfaces/Account'
 | 
				
			||||||
 | 
					import { FetcherError } from '@commerce/utils/errors'
 | 
				
			||||||
 | 
					import normalizeUser from '../utils/normalizations/normalize-user'
 | 
				
			||||||
 | 
					import isLoggedIn from '../utils/tokens/is-logged-in'
 | 
				
			||||||
 | 
					import ensureIToken from '../utils/tokens/ensure-itoken'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useCustomer as UseCustomer<typeof handler>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: SWRHook<CustomerHook> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'account',
 | 
				
			||||||
 | 
					    query: 'get',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useCustomer fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isLoggedIn()) {
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const token: IToken | undefined = ensureIToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!token) {
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const { data: spreeAccountInfoSuccessResponse } = await fetch<
 | 
				
			||||||
 | 
					        GraphQLFetcherResult<IAccount>
 | 
				
			||||||
 | 
					      >({
 | 
				
			||||||
 | 
					        variables: {
 | 
				
			||||||
 | 
					          methodPath: 'account.accountInfo',
 | 
				
			||||||
 | 
					          arguments: [token],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const spreeUser = spreeAccountInfoSuccessResponse.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const normalizedUser = normalizeUser(
 | 
				
			||||||
 | 
					        spreeAccountInfoSuccessResponse,
 | 
				
			||||||
 | 
					        spreeUser
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return normalizedUser
 | 
				
			||||||
 | 
					    } catch (fetchUserError) {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        !(fetchUserError instanceof FetcherError) ||
 | 
				
			||||||
 | 
					        fetchUserError.status !== 404
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        throw fetchUserError
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ useData }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<SWRHook<CustomerHook>['useHook']> = (
 | 
				
			||||||
 | 
					      input
 | 
				
			||||||
 | 
					    ) => {
 | 
				
			||||||
 | 
					      return useData({
 | 
				
			||||||
 | 
					        swrOptions: {
 | 
				
			||||||
 | 
					          revalidateOnFocus: false,
 | 
				
			||||||
 | 
					          ...input?.swrOptions,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/AccessTokenError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/AccessTokenError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class AccessTokenError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/MisconfigurationError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/MisconfigurationError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class MisconfigurationError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/MissingConfigurationValueError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/MissingConfigurationValueError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class MissingConfigurationValueError extends Error {}
 | 
				
			||||||
							
								
								
									
										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
									
								
								framework/spree/errors/MissingOptionValueError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/MissingOptionValueError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class MissingOptionValueError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/MissingPrimaryVariantError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/MissingPrimaryVariantError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class MissingPrimaryVariantError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/MissingProductError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/MissingProductError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class MissingProductError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/MissingSlugVariableError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/MissingSlugVariableError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class MissingSlugVariableError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/MissingVariantError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/MissingVariantError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class MissingVariantError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/RefreshTokenError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/RefreshTokenError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class RefreshTokenError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/SpreeResponseContentError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/SpreeResponseContentError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class SpreeResponseContentError extends Error {}
 | 
				
			||||||
@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class SpreeSdkMethodFromEndpointPathError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/TokensNotRejectedError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/TokensNotRejectedError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class TokensNotRejectedError extends Error {}
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/errors/UserTokenResponseParseError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/errors/UserTokenResponseParseError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default class UserTokenResponseParseError extends Error {}
 | 
				
			||||||
							
								
								
									
										116
									
								
								framework/spree/fetcher.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								framework/spree/fetcher.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					import type { Fetcher } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import convertSpreeErrorToGraphQlError from './utils/convert-spree-error-to-graph-ql-error'
 | 
				
			||||||
 | 
					import { makeClient, errors } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
 | 
				
			||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import { requireConfigValue } from './isomorphic-config'
 | 
				
			||||||
 | 
					import getSpreeSdkMethodFromEndpointPath from './utils/get-spree-sdk-method-from-endpoint-path'
 | 
				
			||||||
 | 
					import SpreeSdkMethodFromEndpointPathError from './errors/SpreeSdkMethodFromEndpointPathError'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  FetcherVariables,
 | 
				
			||||||
 | 
					  SpreeSdkResponse,
 | 
				
			||||||
 | 
					  SpreeSdkResponseWithRawResponse,
 | 
				
			||||||
 | 
					} from './types'
 | 
				
			||||||
 | 
					import createCustomizedFetchFetcher, {
 | 
				
			||||||
 | 
					  fetchResponseKey,
 | 
				
			||||||
 | 
					} from './utils/create-customized-fetch-fetcher'
 | 
				
			||||||
 | 
					import ensureFreshUserAccessToken from './utils/tokens/ensure-fresh-user-access-token'
 | 
				
			||||||
 | 
					import RefreshTokenError from './errors/RefreshTokenError'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const client = makeClient({
 | 
				
			||||||
 | 
					  host: requireConfigValue('apiHost') as string,
 | 
				
			||||||
 | 
					  createFetcher: (fetcherOptions) => {
 | 
				
			||||||
 | 
					    return createCustomizedFetchFetcher({
 | 
				
			||||||
 | 
					      fetch: globalThis.fetch,
 | 
				
			||||||
 | 
					      requestConstructor: globalThis.Request,
 | 
				
			||||||
 | 
					      ...fetcherOptions,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const normalizeSpreeSuccessResponse = (
 | 
				
			||||||
 | 
					  storeResponse: ResultResponse<SpreeSdkResponseWithRawResponse>
 | 
				
			||||||
 | 
					): GraphQLFetcherResult<SpreeSdkResponse> => {
 | 
				
			||||||
 | 
					  const data = storeResponse.success()
 | 
				
			||||||
 | 
					  const rawFetchResponse = data[fetchResponseKey]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    data,
 | 
				
			||||||
 | 
					    res: rawFetchResponse,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fetcher: Fetcher<GraphQLFetcherResult<SpreeSdkResponse>> = async (
 | 
				
			||||||
 | 
					  requestOptions
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  const { url, method, variables, query } = requestOptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log(
 | 
				
			||||||
 | 
					    'Fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					    'url = ',
 | 
				
			||||||
 | 
					    url,
 | 
				
			||||||
 | 
					    'requestOptions = ',
 | 
				
			||||||
 | 
					    requestOptions
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!variables) {
 | 
				
			||||||
 | 
					    throw new SpreeSdkMethodFromEndpointPathError(
 | 
				
			||||||
 | 
					      `Required FetcherVariables not provided.`
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    methodPath,
 | 
				
			||||||
 | 
					    arguments: args,
 | 
				
			||||||
 | 
					    refreshExpiredAccessToken = true,
 | 
				
			||||||
 | 
					    replayUnauthorizedRequest = true,
 | 
				
			||||||
 | 
					  } = variables as FetcherVariables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (refreshExpiredAccessToken) {
 | 
				
			||||||
 | 
					    await ensureFreshUserAccessToken(client)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreeSdkMethod = getSpreeSdkMethodFromEndpointPath(client, methodPath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const storeResponse: ResultResponse<SpreeSdkResponseWithRawResponse> =
 | 
				
			||||||
 | 
					    await spreeSdkMethod(...args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (storeResponse.isSuccess()) {
 | 
				
			||||||
 | 
					    return normalizeSpreeSuccessResponse(storeResponse)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const storeResponseError = storeResponse.fail()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    storeResponseError instanceof errors.SpreeError &&
 | 
				
			||||||
 | 
					    storeResponseError.serverResponse.status === 401 &&
 | 
				
			||||||
 | 
					    replayUnauthorizedRequest
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'Request ended with 401. Replaying request after refreshing the user token.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await ensureFreshUserAccessToken(client)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const replayedStoreResponse: ResultResponse<SpreeSdkResponseWithRawResponse> =
 | 
				
			||||||
 | 
					      await spreeSdkMethod(...args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (replayedStoreResponse.isSuccess()) {
 | 
				
			||||||
 | 
					      return normalizeSpreeSuccessResponse(replayedStoreResponse)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.warn('Replaying the request failed', replayedStoreResponse.fail())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw new RefreshTokenError(
 | 
				
			||||||
 | 
					      'Could not authorize request with current access token.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (storeResponseError instanceof errors.SpreeError) {
 | 
				
			||||||
 | 
					    throw convertSpreeErrorToGraphQlError(storeResponseError)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  throw storeResponseError
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default fetcher
 | 
				
			||||||
							
								
								
									
										49
									
								
								framework/spree/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								framework/spree/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import type { ComponentType, FunctionComponent } from 'react'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Provider,
 | 
				
			||||||
 | 
					  CommerceProviderProps,
 | 
				
			||||||
 | 
					  CoreCommerceProvider,
 | 
				
			||||||
 | 
					  useCommerce as useCoreCommerce,
 | 
				
			||||||
 | 
					} from '@commerce'
 | 
				
			||||||
 | 
					import { spreeProvider } from './provider'
 | 
				
			||||||
 | 
					import type { SpreeProvider } from './provider'
 | 
				
			||||||
 | 
					import { SWRConfig } from 'swr'
 | 
				
			||||||
 | 
					import handleTokenErrors from './utils/handle-token-errors'
 | 
				
			||||||
 | 
					import useLogout from '@commerce/auth/use-logout'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { spreeProvider }
 | 
				
			||||||
 | 
					export type { SpreeProvider }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const WithTokenErrorsHandling: FunctionComponent = ({ children }) => {
 | 
				
			||||||
 | 
					  const logout = useLogout()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <SWRConfig
 | 
				
			||||||
 | 
					      value={{
 | 
				
			||||||
 | 
					        onError: (error, _key) => {
 | 
				
			||||||
 | 
					          handleTokenErrors(error, () => void logout())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </SWRConfig>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getCommerceProvider = <P extends Provider>(provider: P) => {
 | 
				
			||||||
 | 
					  return function CommerceProvider({
 | 
				
			||||||
 | 
					    children,
 | 
				
			||||||
 | 
					    ...props
 | 
				
			||||||
 | 
					  }: CommerceProviderProps) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <CoreCommerceProvider provider={{ ...provider, ...props }}>
 | 
				
			||||||
 | 
					        <WithTokenErrorsHandling>{children}</WithTokenErrorsHandling>
 | 
				
			||||||
 | 
					      </CoreCommerceProvider>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const CommerceProvider =
 | 
				
			||||||
 | 
					  getCommerceProvider<SpreeProvider>(spreeProvider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useCommerce = () => useCoreCommerce<SpreeProvider>()
 | 
				
			||||||
							
								
								
									
										81
									
								
								framework/spree/isomorphic-config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								framework/spree/isomorphic-config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					import forceIsomorphicConfigValues from './utils/force-isomorphic-config-values'
 | 
				
			||||||
 | 
					import requireConfig from './utils/require-config'
 | 
				
			||||||
 | 
					import validateAllProductsTaxonomyId from './utils/validations/validate-all-products-taxonomy-id'
 | 
				
			||||||
 | 
					import validateCookieExpire from './utils/validations/validate-cookie-expire'
 | 
				
			||||||
 | 
					import validateImagesOptionFilter from './utils/validations/validate-images-option-filter'
 | 
				
			||||||
 | 
					import validatePlaceholderImageUrl from './utils/validations/validate-placeholder-image-url'
 | 
				
			||||||
 | 
					import validateProductsPrerenderCount from './utils/validations/validate-products-prerender-count'
 | 
				
			||||||
 | 
					import validateImagesSize from './utils/validations/validate-images-size'
 | 
				
			||||||
 | 
					import validateImagesQuality from './utils/validations/validate-images-quality'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isomorphicConfig = {
 | 
				
			||||||
 | 
					  apiHost: process.env.NEXT_PUBLIC_SPREE_API_HOST,
 | 
				
			||||||
 | 
					  defaultLocale: process.env.NEXT_PUBLIC_SPREE_DEFAULT_LOCALE,
 | 
				
			||||||
 | 
					  cartCookieName: process.env.NEXT_PUBLIC_SPREE_CART_COOKIE_NAME,
 | 
				
			||||||
 | 
					  cartCookieExpire: validateCookieExpire(
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_CART_COOKIE_EXPIRE
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  userCookieName: process.env.NEXT_PUBLIC_SPREE_USER_COOKIE_NAME,
 | 
				
			||||||
 | 
					  userCookieExpire: validateCookieExpire(
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_CART_COOKIE_EXPIRE
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  imageHost: process.env.NEXT_PUBLIC_SPREE_IMAGE_HOST,
 | 
				
			||||||
 | 
					  categoriesTaxonomyPermalink:
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_CATEGORIES_TAXONOMY_PERMALINK,
 | 
				
			||||||
 | 
					  brandsTaxonomyPermalink:
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_BRANDS_TAXONOMY_PERMALINK,
 | 
				
			||||||
 | 
					  allProductsTaxonomyId: validateAllProductsTaxonomyId(
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_ALL_PRODUCTS_TAXONOMY_ID
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  showSingleVariantOptions:
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_SHOW_SINGLE_VARIANT_OPTIONS === 'true',
 | 
				
			||||||
 | 
					  lastUpdatedProductsPrerenderCount: validateProductsPrerenderCount(
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_LAST_UPDATED_PRODUCTS_PRERENDER_COUNT
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  productPlaceholderImageUrl: validatePlaceholderImageUrl(
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_PRODUCT_PLACEHOLDER_IMAGE_URL
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  lineItemPlaceholderImageUrl: validatePlaceholderImageUrl(
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_LINE_ITEM_PLACEHOLDER_IMAGE_URL
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  imagesOptionFilter: validateImagesOptionFilter(
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_IMAGES_OPTION_FILTER
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  imagesSize: validateImagesSize(process.env.NEXT_PUBLIC_SPREE_IMAGES_SIZE),
 | 
				
			||||||
 | 
					  imagesQuality: validateImagesQuality(
 | 
				
			||||||
 | 
					    process.env.NEXT_PUBLIC_SPREE_IMAGES_QUALITY
 | 
				
			||||||
 | 
					  ),
 | 
				
			||||||
 | 
					  loginAfterSignup: process.env.NEXT_PUBLIC_SPREE_LOGIN_AFTER_SIGNUP === 'true',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default forceIsomorphicConfigValues(
 | 
				
			||||||
 | 
					  isomorphicConfig,
 | 
				
			||||||
 | 
					  [],
 | 
				
			||||||
 | 
					  [
 | 
				
			||||||
 | 
					    'apiHost',
 | 
				
			||||||
 | 
					    'defaultLocale',
 | 
				
			||||||
 | 
					    'cartCookieName',
 | 
				
			||||||
 | 
					    'cartCookieExpire',
 | 
				
			||||||
 | 
					    'userCookieName',
 | 
				
			||||||
 | 
					    'userCookieExpire',
 | 
				
			||||||
 | 
					    'imageHost',
 | 
				
			||||||
 | 
					    'categoriesTaxonomyPermalink',
 | 
				
			||||||
 | 
					    'brandsTaxonomyPermalink',
 | 
				
			||||||
 | 
					    'allProductsTaxonomyId',
 | 
				
			||||||
 | 
					    'showSingleVariantOptions',
 | 
				
			||||||
 | 
					    'lastUpdatedProductsPrerenderCount',
 | 
				
			||||||
 | 
					    'productPlaceholderImageUrl',
 | 
				
			||||||
 | 
					    'lineItemPlaceholderImageUrl',
 | 
				
			||||||
 | 
					    'imagesOptionFilter',
 | 
				
			||||||
 | 
					    'imagesSize',
 | 
				
			||||||
 | 
					    'imagesQuality',
 | 
				
			||||||
 | 
					    'loginAfterSignup',
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type IsomorphicConfig = typeof isomorphicConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const requireConfigValue = (key: keyof IsomorphicConfig) =>
 | 
				
			||||||
 | 
					  requireConfig<IsomorphicConfig>(isomorphicConfig, key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { requireConfigValue }
 | 
				
			||||||
							
								
								
									
										16
									
								
								framework/spree/next.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								framework/spree/next.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					const commerce = require('./commerce.config.json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  commerce,
 | 
				
			||||||
 | 
					  images: {
 | 
				
			||||||
 | 
					    domains: [process.env.NEXT_PUBLIC_SPREE_ALLOWED_IMAGE_DOMAIN],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  rewrites() {
 | 
				
			||||||
 | 
					    return [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        source: '/checkout',
 | 
				
			||||||
 | 
					        destination: '/api/checkout',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								framework/spree/product/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								framework/spree/product/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					export { default as usePrice } from './use-price'
 | 
				
			||||||
 | 
					export { default as useSearch } from './use-search'
 | 
				
			||||||
							
								
								
									
										2
									
								
								framework/spree/product/use-price.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								framework/spree/product/use-price.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					export * from '@commerce/product/use-price'
 | 
				
			||||||
 | 
					export { default } from '@commerce/product/use-price'
 | 
				
			||||||
							
								
								
									
										101
									
								
								framework/spree/product/use-search.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								framework/spree/product/use-search.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					import type { SWRHook } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import useSearch from '@commerce/product/use-search'
 | 
				
			||||||
 | 
					import type { Product, SearchProductsHook } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type { UseSearch } from '@commerce/product/use-search'
 | 
				
			||||||
 | 
					import normalizeProduct from '../utils/normalizations/normalize-product'
 | 
				
			||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import { IProducts } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../isomorphic-config'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imagesSize = requireConfigValue('imagesSize') as string
 | 
				
			||||||
 | 
					const imagesQuality = requireConfigValue('imagesQuality') as number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const nextToSpreeSortMap: { [key: string]: string } = {
 | 
				
			||||||
 | 
					  'trending-desc': 'available_on',
 | 
				
			||||||
 | 
					  'latest-desc': 'updated_at',
 | 
				
			||||||
 | 
					  'price-asc': 'price',
 | 
				
			||||||
 | 
					  'price-desc': '-price',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const handler: SWRHook<SearchProductsHook> = {
 | 
				
			||||||
 | 
					  // Provide fetchOptions for SWR cache key
 | 
				
			||||||
 | 
					  fetchOptions: {
 | 
				
			||||||
 | 
					    url: 'products',
 | 
				
			||||||
 | 
					    query: 'list',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  async fetcher({ input, options, fetch }) {
 | 
				
			||||||
 | 
					    // This method is only needed if the options need to be modified before calling the generic fetcher (created in createFetcher).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.info(
 | 
				
			||||||
 | 
					      'useSearch fetcher called. Configuration: ',
 | 
				
			||||||
 | 
					      'input: ',
 | 
				
			||||||
 | 
					      input,
 | 
				
			||||||
 | 
					      'options: ',
 | 
				
			||||||
 | 
					      options
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const taxons = [input.categoryId, input.brandId].filter(Boolean)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const filter = {
 | 
				
			||||||
 | 
					      filter: {
 | 
				
			||||||
 | 
					        ...(taxons.length > 0 ? { taxons: taxons.join(',') } : {}),
 | 
				
			||||||
 | 
					        ...(input.search ? { name: input.search } : {}),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sort = input.sort ? { sort: nextToSpreeSortMap[input.sort] } : {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data: spreeSuccessResponse } = await fetch<
 | 
				
			||||||
 | 
					      GraphQLFetcherResult<IProducts>
 | 
				
			||||||
 | 
					    >({
 | 
				
			||||||
 | 
					      variables: {
 | 
				
			||||||
 | 
					        methodPath: 'products.list',
 | 
				
			||||||
 | 
					        arguments: [
 | 
				
			||||||
 | 
					          {},
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            include:
 | 
				
			||||||
 | 
					              'primary_variant,variants,images,option_types,variants.option_values',
 | 
				
			||||||
 | 
					            per_page: 50,
 | 
				
			||||||
 | 
					            ...filter,
 | 
				
			||||||
 | 
					            ...sort,
 | 
				
			||||||
 | 
					            image_transformation: {
 | 
				
			||||||
 | 
					              quality: imagesQuality,
 | 
				
			||||||
 | 
					              size: imagesSize,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const normalizedProducts: Product[] = spreeSuccessResponse.data.map(
 | 
				
			||||||
 | 
					      (spreeProduct) => normalizeProduct(spreeSuccessResponse, spreeProduct)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const found = spreeSuccessResponse.data.length > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { products: normalizedProducts, found }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  useHook: ({ useData }) => {
 | 
				
			||||||
 | 
					    const useWrappedHook: ReturnType<SWRHook<SearchProductsHook>['useHook']> = (
 | 
				
			||||||
 | 
					      input = {}
 | 
				
			||||||
 | 
					    ) => {
 | 
				
			||||||
 | 
					      return useData({
 | 
				
			||||||
 | 
					        input: [
 | 
				
			||||||
 | 
					          ['search', input.search],
 | 
				
			||||||
 | 
					          ['categoryId', input.categoryId],
 | 
				
			||||||
 | 
					          ['brandId', input.brandId],
 | 
				
			||||||
 | 
					          ['sort', input.sort],
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        swrOptions: {
 | 
				
			||||||
 | 
					          revalidateOnFocus: false,
 | 
				
			||||||
 | 
					          // revalidateOnFocus: false means do not fetch products again when website is refocused in the web browser.
 | 
				
			||||||
 | 
					          ...input.swrOptions,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return useWrappedHook
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default useSearch as UseSearch<typeof handler>
 | 
				
			||||||
							
								
								
									
										35
									
								
								framework/spree/provider.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								framework/spree/provider.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import fetcher from './fetcher'
 | 
				
			||||||
 | 
					import { handler as useCart } from './cart/use-cart'
 | 
				
			||||||
 | 
					import { handler as useAddItem } from './cart/use-add-item'
 | 
				
			||||||
 | 
					import { handler as useUpdateItem } from './cart/use-update-item'
 | 
				
			||||||
 | 
					import { handler as useRemoveItem } from './cart/use-remove-item'
 | 
				
			||||||
 | 
					import { handler as useCustomer } from './customer/use-customer'
 | 
				
			||||||
 | 
					import { handler as useSearch } from './product/use-search'
 | 
				
			||||||
 | 
					import { handler as useLogin } from './auth/use-login'
 | 
				
			||||||
 | 
					import { handler as useLogout } from './auth/use-logout'
 | 
				
			||||||
 | 
					import { handler as useSignup } from './auth/use-signup'
 | 
				
			||||||
 | 
					import { handler as useCheckout } from './checkout/use-checkout'
 | 
				
			||||||
 | 
					import { handler as useWishlist } from './wishlist/use-wishlist'
 | 
				
			||||||
 | 
					import { handler as useWishlistAddItem } from './wishlist/use-add-item'
 | 
				
			||||||
 | 
					import { handler as useWishlistRemoveItem } from './wishlist/use-remove-item'
 | 
				
			||||||
 | 
					import { requireConfigValue } from './isomorphic-config'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const spreeProvider = {
 | 
				
			||||||
 | 
					  locale: requireConfigValue('defaultLocale') as string,
 | 
				
			||||||
 | 
					  cartCookie: requireConfigValue('cartCookieName') as string,
 | 
				
			||||||
 | 
					  fetcher,
 | 
				
			||||||
 | 
					  cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
 | 
				
			||||||
 | 
					  customer: { useCustomer },
 | 
				
			||||||
 | 
					  products: { useSearch },
 | 
				
			||||||
 | 
					  auth: { useLogin, useLogout, useSignup },
 | 
				
			||||||
 | 
					  checkout: { useCheckout },
 | 
				
			||||||
 | 
					  wishlist: {
 | 
				
			||||||
 | 
					    useWishlist,
 | 
				
			||||||
 | 
					    useAddItem: useWishlistAddItem,
 | 
				
			||||||
 | 
					    useRemoveItem: useWishlistRemoveItem,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { spreeProvider }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeProvider = typeof spreeProvider
 | 
				
			||||||
							
								
								
									
										164
									
								
								framework/spree/types/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								framework/spree/types/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
				
			|||||||
 | 
					import type { fetchResponseKey } from '../utils/create-customized-fetch-fetcher'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  JsonApiDocument,
 | 
				
			||||||
 | 
					  JsonApiListResponse,
 | 
				
			||||||
 | 
					  JsonApiSingleResponse,
 | 
				
			||||||
 | 
					} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
 | 
				
			||||||
 | 
					import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
 | 
				
			||||||
 | 
					import type { Response } from '@vercel/fetch'
 | 
				
			||||||
 | 
					import type { ProductOption, Product } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  AddItemHook,
 | 
				
			||||||
 | 
					  RemoveItemHook,
 | 
				
			||||||
 | 
					  WishlistItemBody,
 | 
				
			||||||
 | 
					  WishlistTypes,
 | 
				
			||||||
 | 
					} from '@commerce/types/wishlist'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type UnknownObjectValues = Record<string, unknown>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type NonUndefined<T> = T extends undefined ? never : T
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ValueOf<T> = T[keyof T]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeSdkResponse = JsonApiSingleResponse | JsonApiListResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeSdkResponseWithRawResponse = SpreeSdkResponse & {
 | 
				
			||||||
 | 
					  [fetchResponseKey]: Response
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeSdkResultResponseSuccessType = SpreeSdkResponseWithRawResponse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeSdkMethodReturnType<
 | 
				
			||||||
 | 
					  ResultResponseSuccessType extends SpreeSdkResultResponseSuccessType = SpreeSdkResultResponseSuccessType
 | 
				
			||||||
 | 
					> = Promise<ResultResponse<ResultResponseSuccessType>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeSdkMethod<
 | 
				
			||||||
 | 
					  ResultResponseSuccessType extends SpreeSdkResultResponseSuccessType = SpreeSdkResultResponseSuccessType
 | 
				
			||||||
 | 
					> = (...args: any[]) => SpreeSdkMethodReturnType<ResultResponseSuccessType>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SpreeSdkVariables = {
 | 
				
			||||||
 | 
					  methodPath: string
 | 
				
			||||||
 | 
					  arguments: any[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type FetcherVariables = SpreeSdkVariables & {
 | 
				
			||||||
 | 
					  refreshExpiredAccessToken: boolean
 | 
				
			||||||
 | 
					  replayUnauthorizedRequest: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ImageStyle {
 | 
				
			||||||
 | 
					  url: string
 | 
				
			||||||
 | 
					  width: string
 | 
				
			||||||
 | 
					  height: string
 | 
				
			||||||
 | 
					  size: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SpreeProductImage extends JsonApiDocument {
 | 
				
			||||||
 | 
					  attributes: {
 | 
				
			||||||
 | 
					    position: number
 | 
				
			||||||
 | 
					    alt: string
 | 
				
			||||||
 | 
					    original_url: string
 | 
				
			||||||
 | 
					    transformed_url: string | null
 | 
				
			||||||
 | 
					    styles: ImageStyle[]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface OptionTypeAttr extends JsonApiDocument {
 | 
				
			||||||
 | 
					  attributes: {
 | 
				
			||||||
 | 
					    name: string
 | 
				
			||||||
 | 
					    presentation: string
 | 
				
			||||||
 | 
					    position: number
 | 
				
			||||||
 | 
					    created_at: string
 | 
				
			||||||
 | 
					    updated_at: string
 | 
				
			||||||
 | 
					    filterable: boolean
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface LineItemAttr extends JsonApiDocument {
 | 
				
			||||||
 | 
					  attributes: {
 | 
				
			||||||
 | 
					    name: string
 | 
				
			||||||
 | 
					    quantity: number
 | 
				
			||||||
 | 
					    slug: string
 | 
				
			||||||
 | 
					    options_text: string
 | 
				
			||||||
 | 
					    price: string
 | 
				
			||||||
 | 
					    currency: string
 | 
				
			||||||
 | 
					    display_price: string
 | 
				
			||||||
 | 
					    total: string
 | 
				
			||||||
 | 
					    display_total: string
 | 
				
			||||||
 | 
					    adjustment_total: string
 | 
				
			||||||
 | 
					    display_adjustment_total: string
 | 
				
			||||||
 | 
					    additional_tax_total: string
 | 
				
			||||||
 | 
					    display_additional_tax_total: string
 | 
				
			||||||
 | 
					    discounted_amount: string
 | 
				
			||||||
 | 
					    display_discounted_amount: string
 | 
				
			||||||
 | 
					    pre_tax_amount: string
 | 
				
			||||||
 | 
					    display_pre_tax_amount: string
 | 
				
			||||||
 | 
					    promo_total: string
 | 
				
			||||||
 | 
					    display_promo_total: string
 | 
				
			||||||
 | 
					    included_tax_total: string
 | 
				
			||||||
 | 
					    display_inluded_tax_total: string
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface VariantAttr extends JsonApiDocument {
 | 
				
			||||||
 | 
					  attributes: {
 | 
				
			||||||
 | 
					    sku: string
 | 
				
			||||||
 | 
					    price: string
 | 
				
			||||||
 | 
					    currency: string
 | 
				
			||||||
 | 
					    display_price: string
 | 
				
			||||||
 | 
					    weight: string
 | 
				
			||||||
 | 
					    height: string
 | 
				
			||||||
 | 
					    width: string
 | 
				
			||||||
 | 
					    depth: string
 | 
				
			||||||
 | 
					    is_master: boolean
 | 
				
			||||||
 | 
					    options_text: string
 | 
				
			||||||
 | 
					    purchasable: boolean
 | 
				
			||||||
 | 
					    in_stock: boolean
 | 
				
			||||||
 | 
					    backorderable: boolean
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ProductSlugAttr extends JsonApiDocument {
 | 
				
			||||||
 | 
					  attributes: {
 | 
				
			||||||
 | 
					    slug: string
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export interface IProductsSlugs extends JsonApiListResponse {
 | 
				
			||||||
 | 
					  data: ProductSlugAttr[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ExpandedProductOption = ProductOption & { position: number }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type UserOAuthTokens = {
 | 
				
			||||||
 | 
					  refreshToken: string
 | 
				
			||||||
 | 
					  accessToken: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: ExplicitCommerceWishlist is a temporary type
 | 
				
			||||||
 | 
					// derived from tsx views. It will be removed once
 | 
				
			||||||
 | 
					// Wishlist in @commerce/types/wishlist is updated
 | 
				
			||||||
 | 
					// to a more specific type than `any`.
 | 
				
			||||||
 | 
					export type ExplicitCommerceWishlist = {
 | 
				
			||||||
 | 
					  id: string
 | 
				
			||||||
 | 
					  token: string
 | 
				
			||||||
 | 
					  items: {
 | 
				
			||||||
 | 
					    id: string
 | 
				
			||||||
 | 
					    product_id: number
 | 
				
			||||||
 | 
					    variant_id: number
 | 
				
			||||||
 | 
					    product: Product
 | 
				
			||||||
 | 
					  }[]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ExplicitWishlistAddItemHook = AddItemHook<
 | 
				
			||||||
 | 
					  WishlistTypes & {
 | 
				
			||||||
 | 
					    wishlist: ExplicitCommerceWishlist
 | 
				
			||||||
 | 
					    itemBody: WishlistItemBody & {
 | 
				
			||||||
 | 
					      wishlistToken?: string
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ExplicitWishlistRemoveItemHook = RemoveItemHook & {
 | 
				
			||||||
 | 
					  fetcherInput: { wishlistToken?: string }
 | 
				
			||||||
 | 
					  body: { wishlistToken?: string }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					import { FetcherError } from '@commerce/utils/errors'
 | 
				
			||||||
 | 
					import { errors } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const convertSpreeErrorToGraphQlError = (
 | 
				
			||||||
 | 
					  error: errors.SpreeError
 | 
				
			||||||
 | 
					): FetcherError => {
 | 
				
			||||||
 | 
					  if (error instanceof errors.ExpandedSpreeError) {
 | 
				
			||||||
 | 
					    // Assuming error.errors[key] is a list of strings.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ('base' in error.errors) {
 | 
				
			||||||
 | 
					      const baseErrorMessage = error.errors.base as unknown as string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return new FetcherError({
 | 
				
			||||||
 | 
					        status: error.serverResponse.status,
 | 
				
			||||||
 | 
					        message: baseErrorMessage,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const fetcherErrors = Object.keys(error.errors).map((sdkErrorKey) => {
 | 
				
			||||||
 | 
					      const errors = error.errors[sdkErrorKey] as string[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Naively assume sdkErrorKey is a label. Capitalize it for a better
 | 
				
			||||||
 | 
					      // out-of-the-box experience.
 | 
				
			||||||
 | 
					      const capitalizedSdkErrorKey = sdkErrorKey.replace(/^\w/, (firstChar) =>
 | 
				
			||||||
 | 
					        firstChar.toUpperCase()
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        message: `${capitalizedSdkErrorKey} ${errors.join(', ')}`,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new FetcherError({
 | 
				
			||||||
 | 
					      status: error.serverResponse.status,
 | 
				
			||||||
 | 
					      errors: fetcherErrors,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (error instanceof errors.BasicSpreeError) {
 | 
				
			||||||
 | 
					    return new FetcherError({
 | 
				
			||||||
 | 
					      status: error.serverResponse.status,
 | 
				
			||||||
 | 
					      message: error.summary,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return new FetcherError({
 | 
				
			||||||
 | 
					    status: error.serverResponse.status,
 | 
				
			||||||
 | 
					    message: error.message,
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default convertSpreeErrorToGraphQlError
 | 
				
			||||||
							
								
								
									
										105
									
								
								framework/spree/utils/create-customized-fetch-fetcher.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								framework/spree/utils/create-customized-fetch-fetcher.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  errors,
 | 
				
			||||||
 | 
					  request as spreeSdkRequestHelpers,
 | 
				
			||||||
 | 
					} from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import type { CreateCustomizedFetchFetcher } from '@spree/storefront-api-v2-sdk/types/interfaces/CreateCustomizedFetchFetcher'
 | 
				
			||||||
 | 
					import isJsonContentType from './is-json-content-type'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const fetchResponseKey = Symbol('fetch-response-key')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createCustomizedFetchFetcher: CreateCustomizedFetchFetcher = (
 | 
				
			||||||
 | 
					  fetcherOptions
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  const { FetchError } = errors
 | 
				
			||||||
 | 
					  const sharedHeaders = {
 | 
				
			||||||
 | 
					    'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { host, fetch, requestConstructor } = fetcherOptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    fetch: async (fetchOptions) => {
 | 
				
			||||||
 | 
					      // This fetcher always returns request equal null,
 | 
				
			||||||
 | 
					      // because @vercel/fetch doesn't accept a Request object as argument
 | 
				
			||||||
 | 
					      // and it's not used by NJC anyway.
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const { url, params, method, headers, responseParsing } = fetchOptions
 | 
				
			||||||
 | 
					        const absoluteUrl = new URL(url, host)
 | 
				
			||||||
 | 
					        let payload
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (method.toUpperCase()) {
 | 
				
			||||||
 | 
					          case 'PUT':
 | 
				
			||||||
 | 
					          case 'POST':
 | 
				
			||||||
 | 
					          case 'DELETE':
 | 
				
			||||||
 | 
					          case 'PATCH':
 | 
				
			||||||
 | 
					            payload = { body: JSON.stringify(params) }
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					          default:
 | 
				
			||||||
 | 
					            payload = null
 | 
				
			||||||
 | 
					            absoluteUrl.search =
 | 
				
			||||||
 | 
					              spreeSdkRequestHelpers.objectToQuerystring(params)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const request: Request = new requestConstructor(
 | 
				
			||||||
 | 
					          absoluteUrl.toString(),
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            method: method.toUpperCase(),
 | 
				
			||||||
 | 
					            headers: { ...sharedHeaders, ...headers },
 | 
				
			||||||
 | 
					            ...payload,
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          const response: Response = await fetch(request)
 | 
				
			||||||
 | 
					          const responseContentType = response.headers.get('content-type')
 | 
				
			||||||
 | 
					          let data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (responseParsing === 'automatic') {
 | 
				
			||||||
 | 
					            if (responseContentType && isJsonContentType(responseContentType)) {
 | 
				
			||||||
 | 
					              data = await response.json()
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              data = await response.text()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } else if (responseParsing === 'text') {
 | 
				
			||||||
 | 
					            data = await response.text()
 | 
				
			||||||
 | 
					          } else if (responseParsing === 'json') {
 | 
				
			||||||
 | 
					            data = await response.json()
 | 
				
			||||||
 | 
					          } else if (responseParsing === 'stream') {
 | 
				
			||||||
 | 
					            data = await response.body
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (!response.ok) {
 | 
				
			||||||
 | 
					            // Use the "traditional" approach and reject non 2xx responses.
 | 
				
			||||||
 | 
					            throw new FetchError(response, request, data)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          data[fetchResponseKey] = response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return { data }
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					          if (error instanceof FetchError) {
 | 
				
			||||||
 | 
					            throw error
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (!(error instanceof Error)) {
 | 
				
			||||||
 | 
					            throw error
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          throw new FetchError(null, request, null, error.message)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        if (error instanceof FetchError) {
 | 
				
			||||||
 | 
					          throw error
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!(error instanceof Error)) {
 | 
				
			||||||
 | 
					          throw error
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        throw new FetchError(null, null, null, error.message)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default createCustomizedFetchFetcher
 | 
				
			||||||
							
								
								
									
										22
									
								
								framework/spree/utils/create-empty-cart.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								framework/spree/utils/create-empty-cart.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import type { HookFetcherContext } from '@commerce/utils/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 ensureIToken from './tokens/ensure-itoken'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createEmptyCart = (
 | 
				
			||||||
 | 
					  fetch: HookFetcherContext<{
 | 
				
			||||||
 | 
					    data: any
 | 
				
			||||||
 | 
					  }>['fetch']
 | 
				
			||||||
 | 
					): Promise<GraphQLFetcherResult<IOrder>> => {
 | 
				
			||||||
 | 
					  const token: IToken | undefined = ensureIToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return fetch<GraphQLFetcherResult<IOrder>>({
 | 
				
			||||||
 | 
					    variables: {
 | 
				
			||||||
 | 
					      methodPath: 'cart.create',
 | 
				
			||||||
 | 
					      arguments: [token],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default createEmptyCart
 | 
				
			||||||
							
								
								
									
										26
									
								
								framework/spree/utils/create-get-absolute-image-url.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								framework/spree/utils/create-get-absolute-image-url.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import { SpreeProductImage } from '../types'
 | 
				
			||||||
 | 
					import getImageUrl from './get-image-url'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const createGetAbsoluteImageUrl =
 | 
				
			||||||
 | 
					  (host: string, useOriginalImageSize: boolean = true) =>
 | 
				
			||||||
 | 
					  (
 | 
				
			||||||
 | 
					    image: SpreeProductImage,
 | 
				
			||||||
 | 
					    minWidth: number,
 | 
				
			||||||
 | 
					    minHeight: number
 | 
				
			||||||
 | 
					  ): string | null => {
 | 
				
			||||||
 | 
					    let url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (useOriginalImageSize) {
 | 
				
			||||||
 | 
					      url = image.attributes.transformed_url || null
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      url = getImageUrl(image, minWidth, minHeight)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (url === null) {
 | 
				
			||||||
 | 
					      return null
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return `${host}${url}`
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default createGetAbsoluteImageUrl
 | 
				
			||||||
							
								
								
									
										103
									
								
								framework/spree/utils/expand-options.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								framework/spree/utils/expand-options.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					import type { ProductOptionValues } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  JsonApiDocument,
 | 
				
			||||||
 | 
					  JsonApiResponse,
 | 
				
			||||||
 | 
					} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
 | 
				
			||||||
 | 
					import { jsonApi } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
 | 
				
			||||||
 | 
					import SpreeResponseContentError from '../errors/SpreeResponseContentError'
 | 
				
			||||||
 | 
					import type { OptionTypeAttr, ExpandedProductOption } from '../types'
 | 
				
			||||||
 | 
					import sortOptionsByPosition from '../utils/sort-option-types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isColorProductOption = (productOption: ExpandedProductOption) => {
 | 
				
			||||||
 | 
					  return productOption.displayName === 'Color'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const expandOptions = (
 | 
				
			||||||
 | 
					  spreeSuccessResponse: JsonApiResponse,
 | 
				
			||||||
 | 
					  spreeOptionValue: JsonApiDocument,
 | 
				
			||||||
 | 
					  accumulatedOptions: ExpandedProductOption[]
 | 
				
			||||||
 | 
					): ExpandedProductOption[] => {
 | 
				
			||||||
 | 
					  const spreeOptionTypeIdentifier = spreeOptionValue.relationships.option_type
 | 
				
			||||||
 | 
					    .data as RelationType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const existingOptionIndex = accumulatedOptions.findIndex(
 | 
				
			||||||
 | 
					    (option) => option.id == spreeOptionTypeIdentifier.id
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let option: ExpandedProductOption
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (existingOptionIndex === -1) {
 | 
				
			||||||
 | 
					    const spreeOptionType = jsonApi.findDocument<OptionTypeAttr>(
 | 
				
			||||||
 | 
					      spreeSuccessResponse,
 | 
				
			||||||
 | 
					      spreeOptionTypeIdentifier
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!spreeOptionType) {
 | 
				
			||||||
 | 
					      throw new SpreeResponseContentError(
 | 
				
			||||||
 | 
					        `Option type with id ${spreeOptionTypeIdentifier.id} not found.`
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    option = {
 | 
				
			||||||
 | 
					      __typename: 'MultipleChoiceOption',
 | 
				
			||||||
 | 
					      id: spreeOptionType.id,
 | 
				
			||||||
 | 
					      displayName: spreeOptionType.attributes.presentation,
 | 
				
			||||||
 | 
					      position: spreeOptionType.attributes.position,
 | 
				
			||||||
 | 
					      values: [],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const existingOption = accumulatedOptions[existingOptionIndex]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    option = existingOption
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let optionValue: ProductOptionValues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const label = isColorProductOption(option)
 | 
				
			||||||
 | 
					    ? spreeOptionValue.attributes.name
 | 
				
			||||||
 | 
					    : spreeOptionValue.attributes.presentation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const productOptionValueExists = option.values.some(
 | 
				
			||||||
 | 
					    (optionValue: ProductOptionValues) => optionValue.label === label
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!productOptionValueExists) {
 | 
				
			||||||
 | 
					    if (isColorProductOption(option)) {
 | 
				
			||||||
 | 
					      optionValue = {
 | 
				
			||||||
 | 
					        label,
 | 
				
			||||||
 | 
					        hexColors: [spreeOptionValue.attributes.presentation],
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      optionValue = {
 | 
				
			||||||
 | 
					        label,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (existingOptionIndex === -1) {
 | 
				
			||||||
 | 
					      return [
 | 
				
			||||||
 | 
					        ...accumulatedOptions,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          ...option,
 | 
				
			||||||
 | 
					          values: [optionValue],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const expandedOptionValues = [...option.values, optionValue]
 | 
				
			||||||
 | 
					    const expandedOptions = [...accumulatedOptions]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expandedOptions[existingOptionIndex] = {
 | 
				
			||||||
 | 
					      ...option,
 | 
				
			||||||
 | 
					      values: expandedOptionValues,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const sortedOptions = sortOptionsByPosition(expandedOptions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return sortedOptions
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return accumulatedOptions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default expandOptions
 | 
				
			||||||
							
								
								
									
										43
									
								
								framework/spree/utils/force-isomorphic-config-values.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								framework/spree/utils/force-isomorphic-config-values.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					import type { NonUndefined, UnknownObjectValues } from '../types'
 | 
				
			||||||
 | 
					import MisconfigurationError from '../errors/MisconfigurationError'
 | 
				
			||||||
 | 
					import isServer from './is-server'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const generateMisconfigurationErrorMessage = (
 | 
				
			||||||
 | 
					  keys: Array<string | number | symbol>
 | 
				
			||||||
 | 
					) => `${keys.join(', ')} must have a value before running the Framework.`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const forceIsomorphicConfigValues = <
 | 
				
			||||||
 | 
					  X extends keyof T,
 | 
				
			||||||
 | 
					  T extends UnknownObjectValues,
 | 
				
			||||||
 | 
					  H extends Record<X, NonUndefined<T[X]>>
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  config: T,
 | 
				
			||||||
 | 
					  requiredServerKeys: string[],
 | 
				
			||||||
 | 
					  requiredPublicKeys: X[]
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  if (isServer) {
 | 
				
			||||||
 | 
					    const missingServerConfigValues = requiredServerKeys.filter(
 | 
				
			||||||
 | 
					      (requiredServerKey) => typeof config[requiredServerKey] === 'undefined'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (missingServerConfigValues.length > 0) {
 | 
				
			||||||
 | 
					      throw new MisconfigurationError(
 | 
				
			||||||
 | 
					        generateMisconfigurationErrorMessage(missingServerConfigValues)
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const missingPublicConfigValues = requiredPublicKeys.filter(
 | 
				
			||||||
 | 
					    (requiredPublicKey) => typeof config[requiredPublicKey] === 'undefined'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (missingPublicConfigValues.length > 0) {
 | 
				
			||||||
 | 
					    throw new MisconfigurationError(
 | 
				
			||||||
 | 
					      generateMisconfigurationErrorMessage(missingPublicConfigValues)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return config as T & H
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default forceIsomorphicConfigValues
 | 
				
			||||||
							
								
								
									
										44
									
								
								framework/spree/utils/get-image-url.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								framework/spree/utils/get-image-url.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					// Based on https://github.com/spark-solutions/spree2vuestorefront/blob/d88d85ae1bcd2ec99b13b81cd2e3c25600a0216e/src/utils/index.ts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { ImageStyle, SpreeProductImage } from '../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getImageUrl = (
 | 
				
			||||||
 | 
					  image: SpreeProductImage,
 | 
				
			||||||
 | 
					  minWidth: number,
 | 
				
			||||||
 | 
					  _: number
 | 
				
			||||||
 | 
					): string | null => {
 | 
				
			||||||
 | 
					  // every image is still resized in vue-storefront-api, no matter what getImageUrl returns
 | 
				
			||||||
 | 
					  if (image) {
 | 
				
			||||||
 | 
					    const {
 | 
				
			||||||
 | 
					      attributes: { styles },
 | 
				
			||||||
 | 
					    } = image
 | 
				
			||||||
 | 
					    const bestStyleIndex = styles.reduce(
 | 
				
			||||||
 | 
					      (bSIndex: number | null, style: ImageStyle, styleIndex: number) => {
 | 
				
			||||||
 | 
					        // assuming all images are the same dimensions, just scaled
 | 
				
			||||||
 | 
					        if (bSIndex === null) {
 | 
				
			||||||
 | 
					          return 0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const bestStyle = styles[bSIndex]
 | 
				
			||||||
 | 
					        const widthDiff = +bestStyle.width - minWidth
 | 
				
			||||||
 | 
					        const minWidthDiff = +style.width - minWidth
 | 
				
			||||||
 | 
					        if (widthDiff < 0 && minWidthDiff > 0) {
 | 
				
			||||||
 | 
					          return styleIndex
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (widthDiff > 0 && minWidthDiff < 0) {
 | 
				
			||||||
 | 
					          return bSIndex
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return Math.abs(widthDiff) < Math.abs(minWidthDiff)
 | 
				
			||||||
 | 
					          ? bSIndex
 | 
				
			||||||
 | 
					          : styleIndex
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      null
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (bestStyleIndex !== null) {
 | 
				
			||||||
 | 
					      return styles[bestStyleIndex].url
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default getImageUrl
 | 
				
			||||||
							
								
								
									
										25
									
								
								framework/spree/utils/get-media-gallery.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								framework/spree/utils/get-media-gallery.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					// Based on https://github.com/spark-solutions/spree2vuestorefront/blob/d88d85ae1bcd2ec99b13b81cd2e3c25600a0216e/src/utils/index.ts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import type { ProductImage } from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type { SpreeProductImage } from '../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getMediaGallery = (
 | 
				
			||||||
 | 
					  images: SpreeProductImage[],
 | 
				
			||||||
 | 
					  getImageUrl: (
 | 
				
			||||||
 | 
					    image: SpreeProductImage,
 | 
				
			||||||
 | 
					    minWidth: number,
 | 
				
			||||||
 | 
					    minHeight: number
 | 
				
			||||||
 | 
					  ) => string | null
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  return images.reduce<ProductImage[]>((productImages, _, imageIndex) => {
 | 
				
			||||||
 | 
					    const url = getImageUrl(images[imageIndex], 800, 800)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (url) {
 | 
				
			||||||
 | 
					      return [...productImages, { url }]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return productImages
 | 
				
			||||||
 | 
					  }, [])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default getMediaGallery
 | 
				
			||||||
							
								
								
									
										7
									
								
								framework/spree/utils/get-product-path.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								framework/spree/utils/get-product-path.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					import type { ProductSlugAttr } from '../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getProductPath = (partialSpreeProduct: ProductSlugAttr) => {
 | 
				
			||||||
 | 
					  return `/${partialSpreeProduct.attributes.slug}`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default getProductPath
 | 
				
			||||||
@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					import type { Client } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import SpreeSdkMethodFromEndpointPathError from '../errors/SpreeSdkMethodFromEndpointPathError'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  SpreeSdkMethod,
 | 
				
			||||||
 | 
					  SpreeSdkResultResponseSuccessType,
 | 
				
			||||||
 | 
					} from '../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getSpreeSdkMethodFromEndpointPath = <
 | 
				
			||||||
 | 
					  ExactSpreeSdkClientType extends Client,
 | 
				
			||||||
 | 
					  ResultResponseSuccessType extends SpreeSdkResultResponseSuccessType = SpreeSdkResultResponseSuccessType
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  client: ExactSpreeSdkClientType,
 | 
				
			||||||
 | 
					  path: string
 | 
				
			||||||
 | 
					): SpreeSdkMethod<ResultResponseSuccessType> => {
 | 
				
			||||||
 | 
					  const pathParts = path.split('.')
 | 
				
			||||||
 | 
					  const reachedPath: string[] = []
 | 
				
			||||||
 | 
					  let node = <Record<string, unknown>>client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  console.log(`Looking for ${path} in Spree Sdk.`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  while (reachedPath.length < pathParts.length - 1) {
 | 
				
			||||||
 | 
					    const checkedPathPart = pathParts[reachedPath.length]
 | 
				
			||||||
 | 
					    const checkedNode = node[checkedPathPart]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(`Checking part ${checkedPathPart}.`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (typeof checkedNode !== 'object') {
 | 
				
			||||||
 | 
					      throw new SpreeSdkMethodFromEndpointPathError(
 | 
				
			||||||
 | 
					        `Couldn't reach ${path}. Farthest path reached was: ${reachedPath.join(
 | 
				
			||||||
 | 
					          '.'
 | 
				
			||||||
 | 
					        )}.`
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (checkedNode === null) {
 | 
				
			||||||
 | 
					      throw new SpreeSdkMethodFromEndpointPathError(
 | 
				
			||||||
 | 
					        `Path ${path} doesn't exist.`
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    node = <Record<string, unknown>>checkedNode
 | 
				
			||||||
 | 
					    reachedPath.push(checkedPathPart)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const foundEndpointMethod = node[pathParts[reachedPath.length]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    reachedPath.length !== pathParts.length - 1 ||
 | 
				
			||||||
 | 
					    typeof foundEndpointMethod !== 'function'
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    throw new SpreeSdkMethodFromEndpointPathError(
 | 
				
			||||||
 | 
					      `Couldn't reach ${path}. Farthest path reached was: ${reachedPath.join(
 | 
				
			||||||
 | 
					        '.'
 | 
				
			||||||
 | 
					      )}.`
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return foundEndpointMethod.bind(node)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default getSpreeSdkMethodFromEndpointPath
 | 
				
			||||||
							
								
								
									
										14
									
								
								framework/spree/utils/handle-token-errors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								framework/spree/utils/handle-token-errors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import AccessTokenError from '../errors/AccessTokenError'
 | 
				
			||||||
 | 
					import RefreshTokenError from '../errors/RefreshTokenError'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handleTokenErrors = (error: unknown, action: () => void): boolean => {
 | 
				
			||||||
 | 
					  if (error instanceof AccessTokenError || error instanceof RefreshTokenError) {
 | 
				
			||||||
 | 
					    action()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default handleTokenErrors
 | 
				
			||||||
							
								
								
									
										5
									
								
								framework/spree/utils/is-json-content-type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								framework/spree/utils/is-json-content-type.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					const isJsonContentType = (contentType: string): boolean =>
 | 
				
			||||||
 | 
					  contentType.includes('application/json') ||
 | 
				
			||||||
 | 
					  contentType.includes('application/vnd.api+json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default isJsonContentType
 | 
				
			||||||
							
								
								
									
										1
									
								
								framework/spree/utils/is-server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								framework/spree/utils/is-server.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export default typeof window === 'undefined'
 | 
				
			||||||
							
								
								
									
										58
									
								
								framework/spree/utils/login.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								framework/spree/utils/login.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import type { HookFetcherContext } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import type { AuthTokenAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Authentication'
 | 
				
			||||||
 | 
					import type { AssociateCart } from '@spree/storefront-api-v2-sdk/types/interfaces/endpoints/CartClass'
 | 
				
			||||||
 | 
					import type { IOrder } from '@spree/storefront-api-v2-sdk/types/interfaces/Order'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  IOAuthToken,
 | 
				
			||||||
 | 
					  IToken,
 | 
				
			||||||
 | 
					} from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
 | 
				
			||||||
 | 
					import { getCartToken, removeCartToken } from './tokens/cart-token'
 | 
				
			||||||
 | 
					import { setUserTokenResponse } from './tokens/user-token-response'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const login = async (
 | 
				
			||||||
 | 
					  fetch: HookFetcherContext<{
 | 
				
			||||||
 | 
					    data: any
 | 
				
			||||||
 | 
					  }>['fetch'],
 | 
				
			||||||
 | 
					  getTokenParameters: AuthTokenAttr,
 | 
				
			||||||
 | 
					  associateGuestCart: boolean
 | 
				
			||||||
 | 
					): Promise<void> => {
 | 
				
			||||||
 | 
					  const { data: spreeGetTokenSuccessResponse } = await fetch<
 | 
				
			||||||
 | 
					    GraphQLFetcherResult<IOAuthToken>
 | 
				
			||||||
 | 
					  >({
 | 
				
			||||||
 | 
					    variables: {
 | 
				
			||||||
 | 
					      methodPath: 'authentication.getToken',
 | 
				
			||||||
 | 
					      arguments: [getTokenParameters],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setUserTokenResponse(spreeGetTokenSuccessResponse)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (associateGuestCart) {
 | 
				
			||||||
 | 
					    const cartToken = getCartToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (cartToken) {
 | 
				
			||||||
 | 
					      // If the user had a cart as guest still use its contents
 | 
				
			||||||
 | 
					      // after logging in.
 | 
				
			||||||
 | 
					      const accessToken = spreeGetTokenSuccessResponse.access_token
 | 
				
			||||||
 | 
					      const token: IToken = { bearerToken: accessToken }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const associateGuestCartParameters: AssociateCart = {
 | 
				
			||||||
 | 
					        guest_order_token: cartToken,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await fetch<GraphQLFetcherResult<IOrder>>({
 | 
				
			||||||
 | 
					        variables: {
 | 
				
			||||||
 | 
					          methodPath: 'cart.associateGuestCart',
 | 
				
			||||||
 | 
					          arguments: [token, associateGuestCartParameters],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // We no longer need the guest cart token, so let's remove it.
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  removeCartToken()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default login
 | 
				
			||||||
							
								
								
									
										211
									
								
								framework/spree/utils/normalizations/normalize-cart.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								framework/spree/utils/normalizations/normalize-cart.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,211 @@
 | 
				
			|||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  Cart,
 | 
				
			||||||
 | 
					  LineItem,
 | 
				
			||||||
 | 
					  ProductVariant,
 | 
				
			||||||
 | 
					  SelectedOption,
 | 
				
			||||||
 | 
					} from '@commerce/types/cart'
 | 
				
			||||||
 | 
					import MissingLineItemVariantError from '../../errors/MissingLineItemVariantError'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					import type { OrderAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Order'
 | 
				
			||||||
 | 
					import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
 | 
				
			||||||
 | 
					import type { Image } from '@commerce/types/common'
 | 
				
			||||||
 | 
					import { jsonApi } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import createGetAbsoluteImageUrl from '../create-get-absolute-image-url'
 | 
				
			||||||
 | 
					import getMediaGallery from '../get-media-gallery'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  LineItemAttr,
 | 
				
			||||||
 | 
					  OptionTypeAttr,
 | 
				
			||||||
 | 
					  SpreeProductImage,
 | 
				
			||||||
 | 
					  SpreeSdkResponse,
 | 
				
			||||||
 | 
					  VariantAttr,
 | 
				
			||||||
 | 
					} from '../../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const placeholderImage = requireConfigValue('lineItemPlaceholderImageUrl') as
 | 
				
			||||||
 | 
					  | string
 | 
				
			||||||
 | 
					  | false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isColorProductOption = (productOptionType: OptionTypeAttr) => {
 | 
				
			||||||
 | 
					  return productOptionType.attributes.presentation === 'Color'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const normalizeVariant = (
 | 
				
			||||||
 | 
					  spreeSuccessResponse: SpreeSdkResponse,
 | 
				
			||||||
 | 
					  spreeVariant: VariantAttr
 | 
				
			||||||
 | 
					): ProductVariant => {
 | 
				
			||||||
 | 
					  const spreeProduct = jsonApi.findSingleRelationshipDocument<ProductAttr>(
 | 
				
			||||||
 | 
					    spreeSuccessResponse,
 | 
				
			||||||
 | 
					    spreeVariant,
 | 
				
			||||||
 | 
					    'product'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (spreeProduct === null) {
 | 
				
			||||||
 | 
					    throw new MissingLineItemVariantError(
 | 
				
			||||||
 | 
					      `Couldn't find product for variant with id ${spreeVariant.id}.`
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreeVariantImageRecords =
 | 
				
			||||||
 | 
					    jsonApi.findRelationshipDocuments<SpreeProductImage>(
 | 
				
			||||||
 | 
					      spreeSuccessResponse,
 | 
				
			||||||
 | 
					      spreeVariant,
 | 
				
			||||||
 | 
					      'images'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let lineItemImage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const variantImage = getMediaGallery(
 | 
				
			||||||
 | 
					    spreeVariantImageRecords,
 | 
				
			||||||
 | 
					    createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string)
 | 
				
			||||||
 | 
					  )[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (variantImage) {
 | 
				
			||||||
 | 
					    lineItemImage = variantImage
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const spreeProductImageRecords =
 | 
				
			||||||
 | 
					      jsonApi.findRelationshipDocuments<SpreeProductImage>(
 | 
				
			||||||
 | 
					        spreeSuccessResponse,
 | 
				
			||||||
 | 
					        spreeProduct,
 | 
				
			||||||
 | 
					        'images'
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const productImage = getMediaGallery(
 | 
				
			||||||
 | 
					      spreeProductImageRecords,
 | 
				
			||||||
 | 
					      createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string)
 | 
				
			||||||
 | 
					    )[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lineItemImage = productImage
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const image: Image =
 | 
				
			||||||
 | 
					    lineItemImage ??
 | 
				
			||||||
 | 
					    (placeholderImage === false ? undefined : { url: placeholderImage })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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,
 | 
				
			||||||
 | 
					    isInStock: spreeVariant.attributes.in_stock,
 | 
				
			||||||
 | 
					    availableForSale: spreeVariant.attributes.purchasable,
 | 
				
			||||||
 | 
					    ...(spreeVariant.attributes.weight === '0.0'
 | 
				
			||||||
 | 
					      ? {}
 | 
				
			||||||
 | 
					      : {
 | 
				
			||||||
 | 
					          weight: {
 | 
				
			||||||
 | 
					            value: parseFloat(spreeVariant.attributes.weight),
 | 
				
			||||||
 | 
					            unit: 'KILOGRAMS',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					    // TODO: Add height, width and depth when Measurement type allows distance measurements.
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const normalizeLineItem = (
 | 
				
			||||||
 | 
					  spreeSuccessResponse: SpreeSdkResponse,
 | 
				
			||||||
 | 
					  spreeLineItem: LineItemAttr
 | 
				
			||||||
 | 
					): LineItem => {
 | 
				
			||||||
 | 
					  const variant = jsonApi.findSingleRelationshipDocument<VariantAttr>(
 | 
				
			||||||
 | 
					    spreeSuccessResponse,
 | 
				
			||||||
 | 
					    spreeLineItem,
 | 
				
			||||||
 | 
					    'variant'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (variant === null) {
 | 
				
			||||||
 | 
					    throw new MissingLineItemVariantError(
 | 
				
			||||||
 | 
					      `Couldn't find variant for line item with id ${spreeLineItem.id}.`
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const product = jsonApi.findSingleRelationshipDocument<ProductAttr>(
 | 
				
			||||||
 | 
					    spreeSuccessResponse,
 | 
				
			||||||
 | 
					    variant,
 | 
				
			||||||
 | 
					    'product'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (product === null) {
 | 
				
			||||||
 | 
					    throw new MissingLineItemVariantError(
 | 
				
			||||||
 | 
					      `Couldn't find product for variant with id ${variant.id}.`
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // CartItem.tsx expects path without a '/' prefix unlike pages/product/[slug].tsx and others.
 | 
				
			||||||
 | 
					  const path = `${product.attributes.slug}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreeOptionValues = jsonApi.findRelationshipDocuments(
 | 
				
			||||||
 | 
					    spreeSuccessResponse,
 | 
				
			||||||
 | 
					    variant,
 | 
				
			||||||
 | 
					    'option_values'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const options: SelectedOption[] = spreeOptionValues.map(
 | 
				
			||||||
 | 
					    (spreeOptionValue) => {
 | 
				
			||||||
 | 
					      const spreeOptionType =
 | 
				
			||||||
 | 
					        jsonApi.findSingleRelationshipDocument<OptionTypeAttr>(
 | 
				
			||||||
 | 
					          spreeSuccessResponse,
 | 
				
			||||||
 | 
					          spreeOptionValue,
 | 
				
			||||||
 | 
					          'option_type'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (spreeOptionType === null) {
 | 
				
			||||||
 | 
					        throw new MissingLineItemVariantError(
 | 
				
			||||||
 | 
					          `Couldn't find option type of option value with id ${spreeOptionValue.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: product.id,
 | 
				
			||||||
 | 
					    name: spreeLineItem.attributes.name,
 | 
				
			||||||
 | 
					    quantity: spreeLineItem.attributes.quantity,
 | 
				
			||||||
 | 
					    discounts: [], // TODO: Implement when the template starts displaying them.
 | 
				
			||||||
 | 
					    path,
 | 
				
			||||||
 | 
					    variant: normalizeVariant(spreeSuccessResponse, variant),
 | 
				
			||||||
 | 
					    options,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const normalizeCart = (
 | 
				
			||||||
 | 
					  spreeSuccessResponse: SpreeSdkResponse,
 | 
				
			||||||
 | 
					  spreeCart: OrderAttr
 | 
				
			||||||
 | 
					): Cart => {
 | 
				
			||||||
 | 
					  const lineItems = jsonApi
 | 
				
			||||||
 | 
					    .findRelationshipDocuments<LineItemAttr>(
 | 
				
			||||||
 | 
					      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),
 | 
				
			||||||
 | 
					    subtotalPrice: parseFloat(spreeCart.attributes.item_total),
 | 
				
			||||||
 | 
					    totalPrice: parseFloat(spreeCart.attributes.total),
 | 
				
			||||||
 | 
					    customerId: spreeCart.attributes.token,
 | 
				
			||||||
 | 
					    email: spreeCart.attributes.email,
 | 
				
			||||||
 | 
					    discounts: [], // TODO: Implement when the template starts displaying them.
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { normalizeLineItem }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default normalizeCart
 | 
				
			||||||
							
								
								
									
										42
									
								
								framework/spree/utils/normalizations/normalize-page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								framework/spree/utils/normalizations/normalize-page.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					import { Page } from '@commerce/types/page'
 | 
				
			||||||
 | 
					import type { PageAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Page'
 | 
				
			||||||
 | 
					import { SpreeSdkResponse } from '../../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const normalizePage = (
 | 
				
			||||||
 | 
					  _spreeSuccessResponse: SpreeSdkResponse,
 | 
				
			||||||
 | 
					  spreePage: PageAttr,
 | 
				
			||||||
 | 
					  commerceLocales: string[]
 | 
				
			||||||
 | 
					): Page => {
 | 
				
			||||||
 | 
					  // If the locale returned by Spree is not available, search
 | 
				
			||||||
 | 
					  // for a similar one.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreeLocale = spreePage.attributes.locale
 | 
				
			||||||
 | 
					  let usedCommerceLocale: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (commerceLocales.includes(spreeLocale)) {
 | 
				
			||||||
 | 
					    usedCommerceLocale = spreeLocale
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const genericSpreeLocale = spreeLocale.split('-')[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const foundExactGenericLocale = commerceLocales.includes(genericSpreeLocale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (foundExactGenericLocale) {
 | 
				
			||||||
 | 
					      usedCommerceLocale = genericSpreeLocale
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      const foundSimilarLocale = commerceLocales.find((locale) => {
 | 
				
			||||||
 | 
					        return locale.split('-')[0] === genericSpreeLocale
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      usedCommerceLocale = foundSimilarLocale || spreeLocale
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    id: spreePage.id,
 | 
				
			||||||
 | 
					    name: spreePage.attributes.title,
 | 
				
			||||||
 | 
					    url: `/${usedCommerceLocale}/${spreePage.attributes.slug}`,
 | 
				
			||||||
 | 
					    body: spreePage.attributes.content,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default normalizePage
 | 
				
			||||||
							
								
								
									
										240
									
								
								framework/spree/utils/normalizations/normalize-product.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								framework/spree/utils/normalizations/normalize-product.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
				
			|||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  Product,
 | 
				
			||||||
 | 
					  ProductImage,
 | 
				
			||||||
 | 
					  ProductPrice,
 | 
				
			||||||
 | 
					  ProductVariant,
 | 
				
			||||||
 | 
					} from '@commerce/types/product'
 | 
				
			||||||
 | 
					import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
 | 
				
			||||||
 | 
					import type { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
 | 
				
			||||||
 | 
					import { jsonApi } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import { JsonApiDocument } from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
 | 
				
			||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					import createGetAbsoluteImageUrl from '../create-get-absolute-image-url'
 | 
				
			||||||
 | 
					import expandOptions from '../expand-options'
 | 
				
			||||||
 | 
					import getMediaGallery from '../get-media-gallery'
 | 
				
			||||||
 | 
					import getProductPath from '../get-product-path'
 | 
				
			||||||
 | 
					import MissingPrimaryVariantError from '../../errors/MissingPrimaryVariantError'
 | 
				
			||||||
 | 
					import MissingOptionValueError from '../../errors/MissingOptionValueError'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ExpandedProductOption,
 | 
				
			||||||
 | 
					  SpreeSdkResponse,
 | 
				
			||||||
 | 
					  VariantAttr,
 | 
				
			||||||
 | 
					} from '../../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const placeholderImage = requireConfigValue('productPlaceholderImageUrl') as
 | 
				
			||||||
 | 
					  | string
 | 
				
			||||||
 | 
					  | false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const imagesOptionFilter = requireConfigValue('imagesOptionFilter') as
 | 
				
			||||||
 | 
					  | string
 | 
				
			||||||
 | 
					  | false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const normalizeProduct = (
 | 
				
			||||||
 | 
					  spreeSuccessResponse: SpreeSdkResponse,
 | 
				
			||||||
 | 
					  spreeProduct: ProductAttr
 | 
				
			||||||
 | 
					): Product => {
 | 
				
			||||||
 | 
					  const spreePrimaryVariant =
 | 
				
			||||||
 | 
					    jsonApi.findSingleRelationshipDocument<VariantAttr>(
 | 
				
			||||||
 | 
					      spreeSuccessResponse,
 | 
				
			||||||
 | 
					      spreeProduct,
 | 
				
			||||||
 | 
					      'primary_variant'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (spreePrimaryVariant === null) {
 | 
				
			||||||
 | 
					    throw new MissingPrimaryVariantError(
 | 
				
			||||||
 | 
					      `Couldn't find primary variant for product with id ${spreeProduct.id}.`
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sku = spreePrimaryVariant.attributes.sku
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const price: ProductPrice = {
 | 
				
			||||||
 | 
					    value: parseFloat(spreeProduct.attributes.price),
 | 
				
			||||||
 | 
					    currencyCode: spreeProduct.attributes.currency,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const hasNonMasterVariants =
 | 
				
			||||||
 | 
					    (spreeProduct.relationships.variants.data as RelationType[]).length > 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const showOptions =
 | 
				
			||||||
 | 
					    (requireConfigValue('showSingleVariantOptions') as boolean) ||
 | 
				
			||||||
 | 
					    hasNonMasterVariants
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let options: ExpandedProductOption[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreeVariantRecords = jsonApi.findRelationshipDocuments(
 | 
				
			||||||
 | 
					    spreeSuccessResponse,
 | 
				
			||||||
 | 
					    spreeProduct,
 | 
				
			||||||
 | 
					    'variants'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Use variants with option values if available. Fall back to
 | 
				
			||||||
 | 
					  // Spree primary_variant if no explicit variants are present.
 | 
				
			||||||
 | 
					  const spreeOptionsVariantsOrPrimary =
 | 
				
			||||||
 | 
					    spreeVariantRecords.length === 0
 | 
				
			||||||
 | 
					      ? [spreePrimaryVariant]
 | 
				
			||||||
 | 
					      : spreeVariantRecords
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const variants: ProductVariant[] = spreeOptionsVariantsOrPrimary.map(
 | 
				
			||||||
 | 
					    (spreeVariantRecord) => {
 | 
				
			||||||
 | 
					      let variantOptions: ExpandedProductOption[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (showOptions) {
 | 
				
			||||||
 | 
					        const spreeOptionValues = jsonApi.findRelationshipDocuments(
 | 
				
			||||||
 | 
					          spreeSuccessResponse,
 | 
				
			||||||
 | 
					          spreeVariantRecord,
 | 
				
			||||||
 | 
					          'option_values'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Only include options which are used by variants.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        spreeOptionValues.forEach((spreeOptionValue) => {
 | 
				
			||||||
 | 
					          variantOptions = expandOptions(
 | 
				
			||||||
 | 
					            spreeSuccessResponse,
 | 
				
			||||||
 | 
					            spreeOptionValue,
 | 
				
			||||||
 | 
					            variantOptions
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          options = expandOptions(
 | 
				
			||||||
 | 
					            spreeSuccessResponse,
 | 
				
			||||||
 | 
					            spreeOptionValue,
 | 
				
			||||||
 | 
					            options
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        id: spreeVariantRecord.id,
 | 
				
			||||||
 | 
					        options: variantOptions,
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreePrimaryVariantImageRecords = jsonApi.findRelationshipDocuments(
 | 
				
			||||||
 | 
					    spreeSuccessResponse,
 | 
				
			||||||
 | 
					    spreePrimaryVariant,
 | 
				
			||||||
 | 
					    'images'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let spreeVariantImageRecords: JsonApiDocument[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (imagesOptionFilter === false) {
 | 
				
			||||||
 | 
					    spreeVariantImageRecords = spreeVariantRecords.reduce<JsonApiDocument[]>(
 | 
				
			||||||
 | 
					      (accumulatedImageRecords, spreeVariantRecord) => {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					          ...accumulatedImageRecords,
 | 
				
			||||||
 | 
					          ...jsonApi.findRelationshipDocuments(
 | 
				
			||||||
 | 
					            spreeSuccessResponse,
 | 
				
			||||||
 | 
					            spreeVariantRecord,
 | 
				
			||||||
 | 
					            'images'
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      []
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const spreeOptionTypes = jsonApi.findRelationshipDocuments(
 | 
				
			||||||
 | 
					      spreeSuccessResponse,
 | 
				
			||||||
 | 
					      spreeProduct,
 | 
				
			||||||
 | 
					      'option_types'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const imagesFilterOptionType = spreeOptionTypes.find(
 | 
				
			||||||
 | 
					      (spreeOptionType) =>
 | 
				
			||||||
 | 
					        spreeOptionType.attributes.name === imagesOptionFilter
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!imagesFilterOptionType) {
 | 
				
			||||||
 | 
					      console.warn(
 | 
				
			||||||
 | 
					        `Couldn't find option type having name ${imagesOptionFilter} for product with id ${spreeProduct.id}.` +
 | 
				
			||||||
 | 
					          ' Showing no images for this product.'
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      spreeVariantImageRecords = []
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      const imagesOptionTypeFilterId = imagesFilterOptionType.id
 | 
				
			||||||
 | 
					      const includedOptionValuesImagesIds: string[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      spreeVariantImageRecords = spreeVariantRecords.reduce<JsonApiDocument[]>(
 | 
				
			||||||
 | 
					        (accumulatedImageRecords, spreeVariantRecord) => {
 | 
				
			||||||
 | 
					          const spreeVariantOptionValuesIdentifiers: RelationType[] =
 | 
				
			||||||
 | 
					            spreeVariantRecord.relationships.option_values.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const spreeOptionValueOfFilterTypeIdentifier =
 | 
				
			||||||
 | 
					            spreeVariantOptionValuesIdentifiers.find(
 | 
				
			||||||
 | 
					              (spreeVariantOptionValuesIdentifier: RelationType) =>
 | 
				
			||||||
 | 
					                imagesFilterOptionType.relationships.option_values.data.some(
 | 
				
			||||||
 | 
					                  (filterOptionTypeValueIdentifier: RelationType) =>
 | 
				
			||||||
 | 
					                    filterOptionTypeValueIdentifier.id ===
 | 
				
			||||||
 | 
					                    spreeVariantOptionValuesIdentifier.id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (!spreeOptionValueOfFilterTypeIdentifier) {
 | 
				
			||||||
 | 
					            throw new MissingOptionValueError(
 | 
				
			||||||
 | 
					              `Couldn't find option value related to option type with id ${imagesOptionTypeFilterId}.`
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const optionValueImagesAlreadyIncluded =
 | 
				
			||||||
 | 
					            includedOptionValuesImagesIds.includes(
 | 
				
			||||||
 | 
					              spreeOptionValueOfFilterTypeIdentifier.id
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (optionValueImagesAlreadyIncluded) {
 | 
				
			||||||
 | 
					            return accumulatedImageRecords
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          includedOptionValuesImagesIds.push(
 | 
				
			||||||
 | 
					            spreeOptionValueOfFilterTypeIdentifier.id
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return [
 | 
				
			||||||
 | 
					            ...accumulatedImageRecords,
 | 
				
			||||||
 | 
					            ...jsonApi.findRelationshipDocuments(
 | 
				
			||||||
 | 
					              spreeSuccessResponse,
 | 
				
			||||||
 | 
					              spreeVariantRecord,
 | 
				
			||||||
 | 
					              'images'
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        []
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreeImageRecords = [
 | 
				
			||||||
 | 
					    ...spreePrimaryVariantImageRecords,
 | 
				
			||||||
 | 
					    ...spreeVariantImageRecords,
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const productImages = getMediaGallery(
 | 
				
			||||||
 | 
					    spreeImageRecords,
 | 
				
			||||||
 | 
					    createGetAbsoluteImageUrl(requireConfigValue('imageHost') as string)
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const images: ProductImage[] =
 | 
				
			||||||
 | 
					    productImages.length === 0
 | 
				
			||||||
 | 
					      ? placeholderImage === false
 | 
				
			||||||
 | 
					        ? []
 | 
				
			||||||
 | 
					        : [{ url: placeholderImage }]
 | 
				
			||||||
 | 
					      : productImages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const slug = spreeProduct.attributes.slug
 | 
				
			||||||
 | 
					  const path = getProductPath(spreeProduct)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    id: spreeProduct.id,
 | 
				
			||||||
 | 
					    name: spreeProduct.attributes.name,
 | 
				
			||||||
 | 
					    description: spreeProduct.attributes.description,
 | 
				
			||||||
 | 
					    images,
 | 
				
			||||||
 | 
					    variants,
 | 
				
			||||||
 | 
					    options,
 | 
				
			||||||
 | 
					    price,
 | 
				
			||||||
 | 
					    slug,
 | 
				
			||||||
 | 
					    path,
 | 
				
			||||||
 | 
					    sku,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default normalizeProduct
 | 
				
			||||||
							
								
								
									
										16
									
								
								framework/spree/utils/normalizations/normalize-user.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								framework/spree/utils/normalizations/normalize-user.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import type { Customer } from '@commerce/types/customer'
 | 
				
			||||||
 | 
					import type { AccountAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Account'
 | 
				
			||||||
 | 
					import type { SpreeSdkResponse } from '../../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const normalizeUser = (
 | 
				
			||||||
 | 
					  _spreeSuccessResponse: SpreeSdkResponse,
 | 
				
			||||||
 | 
					  spreeUser: AccountAttr
 | 
				
			||||||
 | 
					): Customer => {
 | 
				
			||||||
 | 
					  const email = spreeUser.attributes.email
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    email,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default normalizeUser
 | 
				
			||||||
							
								
								
									
										68
									
								
								framework/spree/utils/normalizations/normalize-wishlist.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								framework/spree/utils/normalizations/normalize-wishlist.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					import MissingProductError from '../../errors/MissingProductError'
 | 
				
			||||||
 | 
					import MissingVariantError from '../../errors/MissingVariantError'
 | 
				
			||||||
 | 
					import { jsonApi } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import type { ProductAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
 | 
				
			||||||
 | 
					import type { WishedItemAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/WishedItem'
 | 
				
			||||||
 | 
					import type { WishlistAttr } from '@spree/storefront-api-v2-sdk/types/interfaces/Wishlist'
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					  ExplicitCommerceWishlist,
 | 
				
			||||||
 | 
					  SpreeSdkResponse,
 | 
				
			||||||
 | 
					  VariantAttr,
 | 
				
			||||||
 | 
					} from '../../types'
 | 
				
			||||||
 | 
					import normalizeProduct from './normalize-product'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const normalizeWishlist = (
 | 
				
			||||||
 | 
					  spreeSuccessResponse: SpreeSdkResponse,
 | 
				
			||||||
 | 
					  spreeWishlist: WishlistAttr
 | 
				
			||||||
 | 
					): ExplicitCommerceWishlist => {
 | 
				
			||||||
 | 
					  const spreeWishedItems = jsonApi.findRelationshipDocuments<WishedItemAttr>(
 | 
				
			||||||
 | 
					    spreeSuccessResponse,
 | 
				
			||||||
 | 
					    spreeWishlist,
 | 
				
			||||||
 | 
					    'wished_items'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const items: ExplicitCommerceWishlist['items'] = spreeWishedItems.map(
 | 
				
			||||||
 | 
					    (spreeWishedItem) => {
 | 
				
			||||||
 | 
					      const spreeWishedVariant =
 | 
				
			||||||
 | 
					        jsonApi.findSingleRelationshipDocument<VariantAttr>(
 | 
				
			||||||
 | 
					          spreeSuccessResponse,
 | 
				
			||||||
 | 
					          spreeWishedItem,
 | 
				
			||||||
 | 
					          'variant'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (spreeWishedVariant === null) {
 | 
				
			||||||
 | 
					        throw new MissingVariantError(
 | 
				
			||||||
 | 
					          `Couldn't find variant for wished item with id ${spreeWishedItem.id}.`
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const spreeWishedProduct =
 | 
				
			||||||
 | 
					        jsonApi.findSingleRelationshipDocument<ProductAttr>(
 | 
				
			||||||
 | 
					          spreeSuccessResponse,
 | 
				
			||||||
 | 
					          spreeWishedVariant,
 | 
				
			||||||
 | 
					          'product'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (spreeWishedProduct === null) {
 | 
				
			||||||
 | 
					        throw new MissingProductError(
 | 
				
			||||||
 | 
					          `Couldn't find product for variant with id ${spreeWishedVariant.id}.`
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        id: spreeWishedItem.id,
 | 
				
			||||||
 | 
					        product_id: parseInt(spreeWishedProduct.id, 10),
 | 
				
			||||||
 | 
					        variant_id: parseInt(spreeWishedVariant.id, 10),
 | 
				
			||||||
 | 
					        product: normalizeProduct(spreeSuccessResponse, spreeWishedProduct),
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    id: spreeWishlist.id,
 | 
				
			||||||
 | 
					    token: spreeWishlist.attributes.token,
 | 
				
			||||||
 | 
					    items,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default normalizeWishlist
 | 
				
			||||||
							
								
								
									
										16
									
								
								framework/spree/utils/require-config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								framework/spree/utils/require-config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import MissingConfigurationValueError from '../errors/MissingConfigurationValueError'
 | 
				
			||||||
 | 
					import type { NonUndefined, ValueOf } from '../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const requireConfig = <T>(isomorphicConfig: T, key: keyof T) => {
 | 
				
			||||||
 | 
					  const valueUnderKey = isomorphicConfig[key]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (typeof valueUnderKey === 'undefined') {
 | 
				
			||||||
 | 
					    throw new MissingConfigurationValueError(
 | 
				
			||||||
 | 
					      `Value for configuration key ${key} was undefined.`
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return valueUnderKey as NonUndefined<ValueOf<T>>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default requireConfig
 | 
				
			||||||
							
								
								
									
										11
									
								
								framework/spree/utils/sort-option-types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								framework/spree/utils/sort-option-types.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import type { ExpandedProductOption } from '../types'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sortOptionsByPosition = (
 | 
				
			||||||
 | 
					  options: ExpandedProductOption[]
 | 
				
			||||||
 | 
					): ExpandedProductOption[] => {
 | 
				
			||||||
 | 
					  return options.sort((firstOption, secondOption) => {
 | 
				
			||||||
 | 
					    return firstOption.position - secondOption.position
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default sortOptionsByPosition
 | 
				
			||||||
							
								
								
									
										21
									
								
								framework/spree/utils/tokens/cart-token.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								framework/spree/utils/tokens/cart-token.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					import Cookies from 'js-cookie'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getCartToken = () =>
 | 
				
			||||||
 | 
					  Cookies.get(requireConfigValue('cartCookieName') as string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setCartToken = (cartToken: string) => {
 | 
				
			||||||
 | 
					  const cookieOptions = {
 | 
				
			||||||
 | 
					    expires: requireConfigValue('cartCookieExpire') as number,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Cookies.set(
 | 
				
			||||||
 | 
					    requireConfigValue('cartCookieName') as string,
 | 
				
			||||||
 | 
					    cartToken,
 | 
				
			||||||
 | 
					    cookieOptions
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeCartToken = () => {
 | 
				
			||||||
 | 
					  Cookies.remove(requireConfigValue('cartCookieName') as string)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import { SpreeSdkResponseWithRawResponse } from '../../types'
 | 
				
			||||||
 | 
					import type { Client } from '@spree/storefront-api-v2-sdk'
 | 
				
			||||||
 | 
					import type { IOAuthToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
 | 
				
			||||||
 | 
					import getSpreeSdkMethodFromEndpointPath from '../get-spree-sdk-method-from-endpoint-path'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  ensureUserTokenResponse,
 | 
				
			||||||
 | 
					  removeUserTokenResponse,
 | 
				
			||||||
 | 
					  setUserTokenResponse,
 | 
				
			||||||
 | 
					} from './user-token-response'
 | 
				
			||||||
 | 
					import AccessTokenError from '../../errors/AccessTokenError'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * If the user has a saved access token, make sure it's not expired
 | 
				
			||||||
 | 
					 * If it is expired, attempt to refresh it.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const ensureFreshUserAccessToken = async (client: Client): Promise<void> => {
 | 
				
			||||||
 | 
					  const userTokenResponse = ensureUserTokenResponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!userTokenResponse) {
 | 
				
			||||||
 | 
					    // There's no user token or it has an invalid format.
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const isAccessTokenExpired =
 | 
				
			||||||
 | 
					    (userTokenResponse.created_at + userTokenResponse.expires_in) * 1000 <
 | 
				
			||||||
 | 
					    Date.now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!isAccessTokenExpired) {
 | 
				
			||||||
 | 
					    return
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreeRefreshAccessTokenSdkMethod = getSpreeSdkMethodFromEndpointPath<
 | 
				
			||||||
 | 
					    Client,
 | 
				
			||||||
 | 
					    SpreeSdkResponseWithRawResponse & IOAuthToken
 | 
				
			||||||
 | 
					  >(client, 'authentication.refreshToken')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const spreeRefreshAccessTokenResponse =
 | 
				
			||||||
 | 
					    await spreeRefreshAccessTokenSdkMethod({
 | 
				
			||||||
 | 
					      refresh_token: userTokenResponse.refresh_token,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (spreeRefreshAccessTokenResponse.isFail()) {
 | 
				
			||||||
 | 
					    removeUserTokenResponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw new AccessTokenError('Could not refresh access token.')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setUserTokenResponse(spreeRefreshAccessTokenResponse.success())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ensureFreshUserAccessToken
 | 
				
			||||||
							
								
								
									
										25
									
								
								framework/spree/utils/tokens/ensure-itoken.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								framework/spree/utils/tokens/ensure-itoken.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import type { IToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
 | 
				
			||||||
 | 
					import { getCartToken } from './cart-token'
 | 
				
			||||||
 | 
					import { ensureUserTokenResponse } from './user-token-response'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ensureIToken = (): IToken | undefined => {
 | 
				
			||||||
 | 
					  const userTokenResponse = ensureUserTokenResponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (userTokenResponse) {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      bearerToken: userTokenResponse.access_token,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const cartToken = getCartToken()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (cartToken) {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      orderToken: cartToken,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return undefined
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ensureIToken
 | 
				
			||||||
							
								
								
									
										9
									
								
								framework/spree/utils/tokens/is-logged-in.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								framework/spree/utils/tokens/is-logged-in.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { ensureUserTokenResponse } from './user-token-response'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isLoggedIn = (): boolean => {
 | 
				
			||||||
 | 
					  const userTokenResponse = ensureUserTokenResponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return !!userTokenResponse
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default isLoggedIn
 | 
				
			||||||
							
								
								
									
										49
									
								
								framework/spree/utils/tokens/revoke-user-tokens.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								framework/spree/utils/tokens/revoke-user-tokens.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import type { GraphQLFetcherResult } from '@commerce/api'
 | 
				
			||||||
 | 
					import type { HookFetcherContext } from '@commerce/utils/types'
 | 
				
			||||||
 | 
					import TokensNotRejectedError from '../../errors/TokensNotRejectedError'
 | 
				
			||||||
 | 
					import type { UserOAuthTokens } from '../../types'
 | 
				
			||||||
 | 
					import type { EmptyObjectResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/EmptyObject'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const revokeUserTokens = async (
 | 
				
			||||||
 | 
					  fetch: HookFetcherContext<{
 | 
				
			||||||
 | 
					    data: any
 | 
				
			||||||
 | 
					  }>['fetch'],
 | 
				
			||||||
 | 
					  userTokens: UserOAuthTokens
 | 
				
			||||||
 | 
					): Promise<void> => {
 | 
				
			||||||
 | 
					  const spreeRevokeTokensResponses = await Promise.allSettled([
 | 
				
			||||||
 | 
					    fetch<GraphQLFetcherResult<EmptyObjectResponse>>({
 | 
				
			||||||
 | 
					      variables: {
 | 
				
			||||||
 | 
					        methodPath: 'authentication.revokeToken',
 | 
				
			||||||
 | 
					        arguments: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            token: userTokens.refreshToken,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    fetch<GraphQLFetcherResult<EmptyObjectResponse>>({
 | 
				
			||||||
 | 
					      variables: {
 | 
				
			||||||
 | 
					        methodPath: 'authentication.revokeToken',
 | 
				
			||||||
 | 
					        arguments: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            token: userTokens.accessToken,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const anyRejected = spreeRevokeTokensResponses.some(
 | 
				
			||||||
 | 
					    (response) => response.status === 'rejected'
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (anyRejected) {
 | 
				
			||||||
 | 
					    throw new TokensNotRejectedError(
 | 
				
			||||||
 | 
					      'Some tokens could not be rejected in Spree.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return undefined
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default revokeUserTokens
 | 
				
			||||||
							
								
								
									
										58
									
								
								framework/spree/utils/tokens/user-token-response.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								framework/spree/utils/tokens/user-token-response.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					import { requireConfigValue } from '../../isomorphic-config'
 | 
				
			||||||
 | 
					import Cookies from 'js-cookie'
 | 
				
			||||||
 | 
					import type { IOAuthToken } from '@spree/storefront-api-v2-sdk/types/interfaces/Token'
 | 
				
			||||||
 | 
					import UserTokenResponseParseError from '../../errors/UserTokenResponseParseError'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getUserTokenResponse = (): IOAuthToken | undefined => {
 | 
				
			||||||
 | 
					  const stringifiedToken = Cookies.get(
 | 
				
			||||||
 | 
					    requireConfigValue('userCookieName') as string
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!stringifiedToken) {
 | 
				
			||||||
 | 
					    return undefined
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const token: IOAuthToken = JSON.parse(stringifiedToken)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return token
 | 
				
			||||||
 | 
					  } catch (parseError) {
 | 
				
			||||||
 | 
					    throw new UserTokenResponseParseError(
 | 
				
			||||||
 | 
					      'Could not parse stored user token response.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Retrieves the saved user token response. If the response fails json parsing,
 | 
				
			||||||
 | 
					 * removes the saved token and returns @type {undefined} instead.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const ensureUserTokenResponse = (): IOAuthToken | undefined => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    return getUserTokenResponse()
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    if (error instanceof UserTokenResponseParseError) {
 | 
				
			||||||
 | 
					      removeUserTokenResponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return undefined
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw error
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const setUserTokenResponse = (token: IOAuthToken) => {
 | 
				
			||||||
 | 
					  const cookieOptions = {
 | 
				
			||||||
 | 
					    expires: requireConfigValue('userCookieExpire') as number,
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Cookies.set(
 | 
				
			||||||
 | 
					    requireConfigValue('userCookieName') as string,
 | 
				
			||||||
 | 
					    JSON.stringify(token),
 | 
				
			||||||
 | 
					    cookieOptions
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const removeUserTokenResponse = () => {
 | 
				
			||||||
 | 
					  Cookies.remove(requireConfigValue('userCookieName') as string)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					const validateAllProductsTaxonomyId = (taxonomyId: unknown): string | false => {
 | 
				
			||||||
 | 
					  if (!taxonomyId || taxonomyId === 'false') {
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (typeof taxonomyId === 'string') {
 | 
				
			||||||
 | 
					    return taxonomyId
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  throw new TypeError('taxonomyId must be a string or falsy.')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default validateAllProductsTaxonomyId
 | 
				
			||||||
							
								
								
									
										21
									
								
								framework/spree/utils/validations/validate-cookie-expire.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								framework/spree/utils/validations/validate-cookie-expire.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					const validateCookieExpire = (expire: unknown): number => {
 | 
				
			||||||
 | 
					  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
 | 
				
			||||||
@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					const validateImagesOptionFilter = (
 | 
				
			||||||
 | 
					  optionTypeNameOrFalse: unknown
 | 
				
			||||||
 | 
					): string | false => {
 | 
				
			||||||
 | 
					  if (!optionTypeNameOrFalse || optionTypeNameOrFalse === 'false') {
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (typeof optionTypeNameOrFalse === 'string') {
 | 
				
			||||||
 | 
					    return optionTypeNameOrFalse
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  throw new TypeError('optionTypeNameOrFalse must be a string or falsy.')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default validateImagesOptionFilter
 | 
				
			||||||
							
								
								
									
										23
									
								
								framework/spree/utils/validations/validate-images-quality.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								framework/spree/utils/validations/validate-images-quality.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					const validateImagesQuality = (quality: unknown): number => {
 | 
				
			||||||
 | 
					  let quality_level: number
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (typeof quality === 'string') {
 | 
				
			||||||
 | 
					    quality_level = parseInt(quality)
 | 
				
			||||||
 | 
					  } else if (typeof quality === 'number') {
 | 
				
			||||||
 | 
					    quality_level = quality
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    throw new TypeError(
 | 
				
			||||||
 | 
					      'prerenderCount count must be a string containing a number or an integer.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (quality_level === NaN) {
 | 
				
			||||||
 | 
					    throw new TypeError(
 | 
				
			||||||
 | 
					      'prerenderCount count must be a string containing a number or an integer.'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return quality_level
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default validateImagesQuality
 | 
				
			||||||
							
								
								
									
										13
									
								
								framework/spree/utils/validations/validate-images-size.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								framework/spree/utils/validations/validate-images-size.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					const validateImagesSize = (size: unknown): string => {
 | 
				
			||||||
 | 
					  if (typeof size !== 'string') {
 | 
				
			||||||
 | 
					    throw new TypeError('size must be a string.')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!size.includes('x') || size.split('x').length != 2) {
 | 
				
			||||||
 | 
					    throw new Error("size must have two numbers separated with an 'x'")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return size
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default validateImagesSize
 | 
				
			||||||
@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					const validatePlaceholderImageUrl = (
 | 
				
			||||||
 | 
					  placeholderUrlOrFalse: unknown
 | 
				
			||||||
 | 
					): string | false => {
 | 
				
			||||||
 | 
					  if (!placeholderUrlOrFalse || placeholderUrlOrFalse === 'false') {
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (typeof placeholderUrlOrFalse === 'string') {
 | 
				
			||||||
 | 
					    return placeholderUrlOrFalse
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  throw new TypeError('placeholderUrlOrFalse must be a string or falsy.')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default validatePlaceholderImageUrl
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user