Skip to content

Commit 5f0cf2d

Browse files
author
Eduardo Marques
committed
feat: add olist provider
1 parent 87134e2 commit 5f0cf2d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+2504
-24
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ yarn-error.log*
3838

3939
# Turborepo
4040
.turbo
41+
42+
#typescript
43+
tsconfig.tsbuildinfo

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)
1717
- Kibo Commerce Demo: https://kibocommerce.vercel.store/
1818
- Commerce.js Demo: https://commercejs.vercel.store/
1919
- SalesForce Cloud Commerce Demo: https://salesforce-cloud-commerce.vercel.store/
20+
- Olist Demo: https://olist.vercel.store/
2021

2122
## Run minimal version locally
2223

packages/commerce/new-provider.md

+24-19
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A commerce provider is a headless e-commerce platform that integrates with the [
1515
- Kibo Commerce ([packages/kibocommerce](../kibocommerce))
1616
- Commerce.js ([packages/commercejs](../commercejs))
1717
- SFCC - SalesForce Cloud Commerce ([packages/sfcc](../sfcc))
18+
- Olist ([packages/olist](../olist))
1819

1920
Adding a commerce provider means adding a new folder in `packages` with a folder structure like the next one:
2021

@@ -69,7 +70,10 @@ Then, open [/site/.env.template](/site/.env.template) and add the provider name
6970
Using BigCommerce as an example. The first thing to do is export a `CommerceProvider` component that includes a `provider` object with all the handlers that can be used for hooks:
7071

7172
```tsx
72-
import { getCommerceProvider, useCommerce as useCoreCommerce } from '@vercel/commerce'
73+
import {
74+
getCommerceProvider,
75+
useCommerce as useCoreCommerce,
76+
} from '@vercel/commerce'
7377
import { bigcommerceProvider, BigcommerceProvider } from './provider'
7478

7579
export { bigcommerceProvider }
@@ -213,25 +217,26 @@ export const handler: MutationHook<AddItemHook> = {
213217
```
214218

215219
## Showing progress and features
220+
216221
When creating a PR for a new provider, include this list in the PR description and mark the progress as you push so we can organize the code review. Not all points are required (but advised) so make sure to keep the list up to date.
217222

218223
**Status**
219224

220-
* [ ] CommerceProvider
221-
* [ ] Schema & TS types
222-
* [ ] API Operations - Get all collections
223-
* [ ] API Operations - Get all pages
224-
* [ ] API Operations - Get all products
225-
* [ ] API Operations - Get page
226-
* [ ] API Operations - Get product
227-
* [ ] API Operations - Get Shop Info (categories and vendors working — `vendors` query still a WIP PR on Reaction)
228-
* [ ] Hook - Add Item
229-
* [ ] Hook - Remove Item
230-
* [ ] Hook - Update Item
231-
* [ ] Hook - Get Cart (account-tied carts working, anonymous carts working, cart reconciliation working)
232-
* [ ] Auth (based on a WIP PR on Reaction - still need to implement refresh tokens)
233-
* [ ] Customer information
234-
* [ ] Product attributes - Size, Colors
235-
* [ ] Custom checkout
236-
* [ ] Typing (in progress)
237-
* [ ] Tests
225+
- [ ] CommerceProvider
226+
- [ ] Schema & TS types
227+
- [ ] API Operations - Get all collections
228+
- [ ] API Operations - Get all pages
229+
- [ ] API Operations - Get all products
230+
- [ ] API Operations - Get page
231+
- [ ] API Operations - Get product
232+
- [ ] API Operations - Get Shop Info (categories and vendors working — `vendors` query still a WIP PR on Reaction)
233+
- [ ] Hook - Add Item
234+
- [ ] Hook - Remove Item
235+
- [ ] Hook - Update Item
236+
- [ ] Hook - Get Cart (account-tied carts working, anonymous carts working, cart reconciliation working)
237+
- [ ] Auth (based on a WIP PR on Reaction - still need to implement refresh tokens)
238+
- [ ] Customer information
239+
- [ ] Product attributes - Size, Colors
240+
- [ ] Custom checkout
241+
- [ ] Typing (in progress)
242+
- [ ] Tests

packages/olist/.env.template

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
COMMERCE_PROVIDER=@vercel/commerce-olist
2+
3+
NEXT_PUBLIC_OLIST_STOREFRONT_DOMAIN=
4+
NEXT_PUBLIC_OLIST_STOREFRONT_ACCESS_TOKEN=

packages/olist/.prettierignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist

packages/olist/.prettierrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"semi": false,
3+
"singleQuote": true,
4+
"tabWidth": 2,
5+
"useTabs": false
6+
}

packages/olist/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Next.js Olist Provider

packages/olist/package.json

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"name": "@vercel/commerce-olist",
3+
"version": "0.0.1",
4+
"license": "MIT",
5+
"scripts": {
6+
"release": "taskr release",
7+
"build": "taskr build",
8+
"dev": "taskr",
9+
"types": "tsc --emitDeclarationOnly",
10+
"prettier-fix": "prettier --write ."
11+
},
12+
"sideEffects": false,
13+
"type": "module",
14+
"exports": {
15+
".": "./dist/index.js",
16+
"./*": [
17+
"./dist/*.js",
18+
"./dist/*/index.js"
19+
],
20+
"./next.config": "./dist/next.config.cjs"
21+
},
22+
"typesVersions": {
23+
"*": {
24+
"*": [
25+
"src/*",
26+
"src/*/index"
27+
],
28+
"next.config": [
29+
"dist/next.config.d.cts"
30+
]
31+
}
32+
},
33+
"files": [
34+
"dist"
35+
],
36+
"publishConfig": {
37+
"typesVersions": {
38+
"*": {
39+
"*": [
40+
"dist/*.d.ts",
41+
"dist/*/index.d.ts"
42+
],
43+
"next.config": [
44+
"dist/next.config.d.cts"
45+
]
46+
}
47+
}
48+
},
49+
"dependencies": {
50+
"@vercel/commerce": "^0.0.1",
51+
"@vercel/fetch": "^6.1.1",
52+
"@vnda/headless-framework": "^0.0.33"
53+
},
54+
"peerDependencies": {
55+
"next": "^12",
56+
"react": "^17",
57+
"react-dom": "^17"
58+
},
59+
"devDependencies": {
60+
"@taskr/clear": "^1.1.0",
61+
"@taskr/esnext": "^1.1.0",
62+
"@taskr/watch": "^1.1.0",
63+
"@types/node": "^17.0.8",
64+
"@types/react": "^17.0.38",
65+
"@types/request-ip": "^0.0.37",
66+
"lint-staged": "^12.1.7",
67+
"next": "^12.0.8",
68+
"prettier": "^2.5.1",
69+
"react": "^17.0.2",
70+
"react-dom": "^17.0.2",
71+
"taskr": "^1.1.0",
72+
"taskr-swc": "^0.0.1",
73+
"typescript": "^4.5.4"
74+
},
75+
"lint-staged": {
76+
"**/*.{js,jsx,ts,tsx,json}": [
77+
"prettier --write",
78+
"git add"
79+
]
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { serialize } from 'cookie'
2+
import type { Cart } from '@vnda/headless-framework'
3+
4+
import type { CartEndpoint, Handler } from '.'
5+
6+
import {
7+
mapCommerceToRawRequest,
8+
mapRawToCommerceResponse,
9+
} from '../../../utils/cart'
10+
11+
const addItem: CartEndpoint['handlers']['addItem'] = async ({
12+
res: response,
13+
body: { cartId, item },
14+
config: { service, cartCookie, cartTokenCookie },
15+
}: Handler) => {
16+
try {
17+
if (!item) {
18+
return response.status(400).json({
19+
data: null,
20+
errors: [{ message: 'Missing item' }],
21+
})
22+
}
23+
24+
if (!item.quantity) item.quantity = 1
25+
26+
let cart: Cart
27+
28+
if (!cartId) {
29+
cart = await service.cart.create()
30+
31+
response.setHeader('Set-Cookie', [
32+
serialize(cartCookie, cart.id.toString(), {
33+
maxAge: 60 * 60 * 24 * 30,
34+
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
35+
secure: process.env.NODE_ENV === 'production',
36+
path: '/',
37+
sameSite: 'lax',
38+
}),
39+
serialize(cartTokenCookie, cart.token, {
40+
maxAge: 60 * 60 * 24 * 30,
41+
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
42+
secure: process.env.NODE_ENV === 'production',
43+
path: '/',
44+
sameSite: 'lax',
45+
}),
46+
])
47+
} else {
48+
cart = await service.cart.getById(Number(cartId))
49+
}
50+
51+
const cartItem = await service.cart.addItem(
52+
cartId ? Number(cartId) : cart!.id,
53+
mapCommerceToRawRequest({
54+
...item,
55+
quantity:
56+
Number(
57+
cart.items.find(({ variantSku }) => variantSku)!.quantity || 0
58+
) + 1 || 1,
59+
})
60+
)
61+
62+
cart.items = [...(cart?.items || []), cartItem]
63+
64+
response.status(200).json({
65+
data: mapRawToCommerceResponse(cart),
66+
errors: [],
67+
})
68+
} catch (error) {
69+
response.status(500).json({
70+
data: {},
71+
errors: error,
72+
})
73+
}
74+
}
75+
76+
export default addItem
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { serialize } from 'cookie'
2+
3+
import type { CartEndpoint, Handler } from '.'
4+
5+
import { mapRawToCommerceResponse } from '../../../utils/cart'
6+
7+
const getCart: CartEndpoint['handlers']['getCart'] = async ({
8+
res: response,
9+
body: { cartId },
10+
config: { service, cartCookie, cartTokenCookie },
11+
}: Handler) => {
12+
if (!cartId) {
13+
return response.status(400).json({
14+
data: null,
15+
errors: [{ message: 'Invalid request' }],
16+
})
17+
}
18+
19+
try {
20+
const cart = await service.cart.getById(Number(cartId))
21+
22+
response
23+
.status(200)
24+
.json({ data: mapRawToCommerceResponse(cart), errors: [] })
25+
} catch (error) {
26+
response.setHeader('Set-Cookie', [
27+
serialize(cartCookie, cartId, {
28+
maxAge: -1,
29+
path: '/',
30+
}),
31+
serialize(cartTokenCookie, cartId, {
32+
maxAge: -1,
33+
path: '/',
34+
}),
35+
])
36+
37+
response.status(200).json({ data: null, errors: [] })
38+
}
39+
}
40+
41+
export default getCart
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import cartEndpoint from '@vercel/commerce/api/endpoints/cart'
2+
import { createEndpoint } from '@vercel/commerce/api'
3+
4+
import type { GetAPISchema } from '@vercel/commerce/api'
5+
import type { CartItemBody, CartSchema } from '@vercel/commerce/types/cart'
6+
7+
import getCart from './get-cart'
8+
import addItem from './add-item'
9+
import updateItem from './update-item'
10+
import removeItem from './remove-item'
11+
12+
import type { OlistAPI } from '../../../api'
13+
import type { Handler as HandlerAPI } from '../../../types/api'
14+
15+
export type CartAPI = GetAPISchema<OlistAPI, CartSchema>
16+
17+
export type CartEndpoint = CartAPI['endpoint']
18+
19+
export const handlers: CartEndpoint['handlers'] = {
20+
getCart,
21+
addItem,
22+
updateItem,
23+
removeItem,
24+
}
25+
26+
type GetCartBody = {
27+
cartId?: string
28+
}
29+
30+
type AddItemBody = {
31+
item?: CartItemBody
32+
}
33+
34+
type RemoveItemBody = {
35+
itemId?: string
36+
}
37+
38+
export type Handler = {
39+
body: GetCartBody & AddItemBody & RemoveItemBody
40+
} & HandlerAPI
41+
42+
const cartApi = createEndpoint<CartAPI>({
43+
handler: cartEndpoint,
44+
handlers,
45+
})
46+
47+
export default cartApi
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { CartEndpoint, Handler } from '.'
2+
3+
import { mapRawToCommerceResponse } from '../../../utils/cart'
4+
5+
const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
6+
res: response,
7+
body: { cartId, itemId },
8+
config: { service },
9+
}: Handler) => {
10+
if (!cartId || !itemId) {
11+
return response.status(400).json({
12+
data: null,
13+
errors: [{ message: 'Invalid request' }],
14+
})
15+
}
16+
17+
await service.cart.removeItem(Number(cartId), Number(itemId))
18+
19+
const cart = await service.cart.getById(Number(cartId))
20+
21+
response
22+
.status(200)
23+
.json({ data: mapRawToCommerceResponse(cart), errors: [] })
24+
}
25+
26+
export default removeItem

0 commit comments

Comments
 (0)