Skip to content

Commit

Permalink
Spree Commerce Provider (vercel#484)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 (vercel#574)

Variable name should be `ChevronRight`

* Update get-cart.ts (vercel#474)

include digital items

Co-authored-by: Gonzalo Pozzo <[email protected]>

* Update normalize.ts (vercel#475)

add missing options property to `normalizeLineItem`

Co-authored-by: Gonzalo Pozzo <[email protected]>

* Update add-item.ts (vercel#473)

* Update add-item.ts

include digital items

* Update add-item.ts

include digital items

Co-authored-by: Gonzalo Pozzo <[email protected]>

* fix typo (vercel#572)

Co-authored-by: Gonzalo Pozzo <[email protected]>

* 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 <[email protected]>

* Change label for link to Spree demo store in Spree's README

Co-authored-by: Gonzalo Pozzo <[email protected]>

* Change URL to Spree demo store in Spree's README

Co-authored-by: Gonzalo Pozzo <[email protected]>

* Use only relative paths to /framework/spree from itself

Co-authored-by: tniezg <[email protected]>
Co-authored-by: Damian Legawiec <[email protected]>
Co-authored-by: Robert Nowakowski <[email protected]>
Co-authored-by: Grey <[email protected]>
Co-authored-by: pfcodes <[email protected]>
Co-authored-by: Gonzalo Pozzo <[email protected]>
Co-authored-by: Konrad Kruk <[email protected]>
  • Loading branch information
8 people authored Dec 13, 2021
1 parent 541009f commit d77d000
Show file tree
Hide file tree
Showing 107 changed files with 4,142 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Available providers: local, bigcommerce, shopify, swell, saleor
# Available providers: local, bigcommerce, shopify, swell, saleor, spree
COMMERCE_PROVIDER=

BIGCOMMERCE_STOREFRONT_API_URL=
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)
- Vendure Demo: https://vendure.vercel.store
- Saleor Demo: https://saleor.vercel.store/
- Ordercloud Demo: https://ordercloud.vercel.store/
- Spree Demo: https://spree.vercel.store/

## Features

Expand All @@ -28,7 +29,7 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)

## 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

Expand Down
1 change: 1 addition & 0 deletions framework/commerce/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const PROVIDERS = [
'swell',
'vendure',
'ordercloud',
'spree',
]

function getProviderName() {
Expand Down
25 changes: 25 additions & 0 deletions framework/spree/.env.template
Original file line number Diff line number Diff line change
@@ -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
Binary file added framework/spree/README-assets/screenshots.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions framework/spree/README.md
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions framework/spree/api/endpoints/cart/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
1 change: 1 addition & 0 deletions framework/spree/api/endpoints/catalog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
1 change: 1 addition & 0 deletions framework/spree/api/endpoints/catalog/products.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
44 changes: 44 additions & 0 deletions framework/spree/api/endpoints/checkout/get-checkout.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 22 additions & 0 deletions framework/spree/api/endpoints/checkout/index.ts
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions framework/spree/api/endpoints/customer/address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
1 change: 1 addition & 0 deletions framework/spree/api/endpoints/customer/card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
1 change: 1 addition & 0 deletions framework/spree/api/endpoints/customer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
1 change: 1 addition & 0 deletions framework/spree/api/endpoints/login/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
1 change: 1 addition & 0 deletions framework/spree/api/endpoints/logout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
1 change: 1 addition & 0 deletions framework/spree/api/endpoints/signup/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
1 change: 1 addition & 0 deletions framework/spree/api/endpoints/wishlist/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}
45 changes: 45 additions & 0 deletions framework/spree/api/index.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 82 additions & 0 deletions framework/spree/api/operations/get-all-pages.ts
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit d77d000

Please sign in to comment.