This commit is contained in:
Oliver Heywood 2022-10-17 09:11:37 -05:00
commit 44eb79bb18
441 changed files with 23980 additions and 15687 deletions

View File

@ -0,0 +1,55 @@
name: Core package Bug Report
description: Create a bug report for the Next.js commerce core package
labels: 'template: core bug'
body:
- type: markdown
attributes:
value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible.
- type: checkboxes
attributes:
label: Verify latest commit
description: `main` is the latest version of Next.js Commerce.
options:
- label: I verified that the issue exists on `main`
required: true
- type: textarea
attributes:
label: Provide environment information
description: Please run `npx --no-install next info` in the root directory of your project and paste the results.
validations:
required: true
- type: input
attributes:
label: What browser are you using? (if relevant)
description: 'Please specify the exact version. For example: Chrome 100.0.4878.0'
- type: input
attributes:
label: How are you deploying your application? (if relevant)
description: 'For example: next start, next export, Vercel, Other platform'
- type: textarea
attributes:
label: Describe the Bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: To Reproduce
description: Steps to reproduce the behavior, please provide a clear code snippets that always reproduces the issue or a GitHub repository. Screenshots can be provided in the issue body below.
validations:
required: true
- type: markdown
attributes:
value: Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear.
- type: markdown
attributes:
value: Contributors should be able to follow the steps provided in order to reproduce the bug.
- type: markdown
attributes:
value: These steps are used to add integration tests to ensure the same issue does not happen again. Thanks in advance!

View File

@ -0,0 +1,59 @@
name: Provider package Bug Report
description: Create a bug report for the Next.js commerce core package
labels: 'template: provider bug'
body:
- type: markdown
attributes:
value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible.
- type: checkboxes
attributes:
label: Verify latest commit
description: `main` is the latest version of Next.js Commerce.
options:
- label: I verified that the issue exists on `main`
required: true
- type: textarea
attributes:
label: Provide environment information
description: Please run `npx --no-install next info` in the root directory of your project and paste the results.
validations:
required: true
- type: input
attributes:
label: What Provider are you using?
description: 'Please specify the provider package name. For example: `bigcommerce`'
- type: input
attributes:
label: What browser are you using? (if relevant)
description: 'Please specify the exact version. For example: Chrome 100.0.4878.0'
- type: input
attributes:
label: How are you deploying your application? (if relevant)
description: 'For example: next start, next export, Vercel, Other platform'
- type: textarea
attributes:
label: Describe the Bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: To Reproduce
description: Steps to reproduce the behavior, please provide a clear code snippets that always reproduces the issue or a GitHub repository. Screenshots can be provided in the issue body below.
validations:
required: true
- type: markdown
attributes:
value: Before posting the issue go through the steps you've written down to make sure the steps provided are detailed and clear.
- type: markdown
attributes:
value: Contributors should be able to follow the steps provided in order to reproduce the bug.
- type: markdown
attributes:
value: These steps are used to add integration tests to ensure the same issue does not happen again. Thanks in advance!

View File

@ -0,0 +1,28 @@
name: Feature Request
description: Create a feature request for the Next.js core
labels: 'template: story'
body:
- type: markdown
attributes:
value: Thanks for taking the time to file a feature request! Please fill out this form as completely as possible.
- type: markdown
attributes:
value: 'Feature requests will be converted to the GitHub Discussions "Ideas" section.'
- type: textarea
attributes:
label: Describe the feature you'd like to request
description: A clear and concise description of what you want and what your use case is.
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: true

View File

@ -0,0 +1,18 @@
name: 'Docs Request for an Update or Improvement'
description: A request to update or improve Next.js Commerce documentation
title: 'Docs: '
labels:
- 'template: documentation'
body:
- type: textarea
attributes:
label: What is the improvement or update you wish to see?
description: 'Example: I would like to see more examples of how to use hooks.'
validations:
required: true
- type: textarea
attributes:
label: Is there any context that might help us understand?
description: A clear description of any added context that might help us understand.
validations:
required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/vercel/commerce/discussions
about: Ask questions and discuss with other community members

3
.gitignore vendored
View File

@ -1,9 +1,8 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies # dependencies
node_modules node_modules
.pnp .pnp
.pnp.js .pnp.js
.pnpm-debug.log
# testing # testing
coverage coverage

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}

View File

@ -1,4 +1,4 @@
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fcommerce&project-name=commerce&repo-name=commerce&demo-title=Next.js%20Commerce&demo-description=An%20all-in-one%20starter%20kit%20for%20high-performance%20e-commerce%20sites.&demo-url=https%3A%2F%2Fdemo.vercel.store&demo-image=https%3A%2F%2Fbigcommerce-demo-asset-ksvtgfvnd.vercel.app%2Fbigcommerce.png&integration-ids=oac_MuWZiE4jtmQ2ejZQaQ7ncuDT) [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fcommerce&project-name=commerce&repo-name=commerce&demo-title=Next.js%20Commerce&demo-description=An%20all-in-one%20starter%20kit%20for%20high-performance%20e-commerce%20sites.&demo-url=https%3A%2F%2Fdemo.vercel.store&demo-image=https%3A%2F%2Fbigcommerce-demo-asset-ksvtgfvnd.vercel.app%2Fbigcommerce.png&integration-ids=oac_MuWZiE4jtmQ2ejZQaQ7ncuDT,oac_9HSKtXld74NG0srzdxSiBGty&skippable-integrations=1&root-directory=site&build-command=cd%20..%20%26%26%20yarn%20build)
# Next.js Commerce # Next.js Commerce
@ -16,6 +16,18 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)
- Spree Demo: https://spree.vercel.store/ - Spree Demo: https://spree.vercel.store/
- Kibo Commerce Demo: https://kibocommerce.vercel.store/ - Kibo Commerce Demo: https://kibocommerce.vercel.store/
- Commerce.js Demo: https://commercejs.vercel.store/ - Commerce.js Demo: https://commercejs.vercel.store/
- SalesForce Cloud Commerce Demo: https://salesforce-cloud-commerce.vercel.store/
## Run minimal version locally
> To run a minimal version of Next.js Commerce you can start with the default local provider `@vercel/commerce-local` that has disabled all features (cart, auth) and use static files for the backend
```bash
pnpm install & pnpm build # run this commands in root folder of the mono repo
pnpm dev # run this commands in the site folder
```
> If you encounter any problems while installing and running for the first time, please see the Troubleshoot section
## Features ## Features
@ -98,11 +110,12 @@ Our commitment to Open Source can be found [here](https://vercel.com/oss).
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device. 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device.
2. Create a new branch `git checkout -b MY_BRANCH_NAME` 2. Create a new branch `git checkout -b MY_BRANCH_NAME`
3. Install the dependencies: `yarn` 3. Install the dependencies: `pnpm install`
4. Duplicate `site/.env.template` and rename it to `site/.env.local` 4. Build the packages: `pnpm build`
5. Add proper store values to `site/.env.local` 5. Duplicate `site/.env.template` and rename it to `site/.env.local`
6. Run `cd site` and `yarn dev` to build and watch for code changes 6. Add proper store values to `site/.env.local`
7. Run `yarn turbo run build` to check the build after your changes 7. Run `cd site` & `pnpm dev` to watch for code changes
8. Run `pnpm turbo run build` to check the build after your changes
## Work in progress ## Work in progress
@ -177,10 +190,10 @@ error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
``` ```
The error usually occurs when running yarn dev inside of the `/site/` folder after installing a fresh repository. The error usually occurs when running `pnpm dev` inside of the `/site/` folder after installing a fresh repository.
In order to fix this, run `yarn dev` in the monorepo root folder first. In order to fix this, run `pnpm build` in the monorepo root folder first.
> Using `pnpm dev` from the root is recommended for developing, which will run watch mode on all packages.
> Using `yarn dev` from the root is recommended for developing, which will run watch mode on all packages.
</details> </details>

View File

@ -2,25 +2,22 @@
"name": "commerce", "name": "commerce",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"workspaces": [
"site",
"packages/*"
],
"scripts": { "scripts": {
"build": "turbo run build --scope=next-commerce --include-dependencies --no-deps", "build": "turbo run build --filter=next-commerce...",
"dev": "turbo run dev", "dev": "turbo run dev",
"start": "turbo run start", "start": "turbo run start",
"types": "turbo run types",
"prettier-fix": "prettier --write ." "prettier-fix": "prettier --write ."
}, },
"devDependencies": { "devDependencies": {
"husky": "^7.0.4", "husky": "^8.0.1",
"prettier": "^2.5.1", "prettier": "^2.7.1",
"turbo": "^1.1.2" "turbo": "^1.4.6"
}, },
"husky": { "husky": {
"hooks": { "hooks": {
"pre-commit": "turbo run lint" "pre-commit": "turbo run lint"
} }
}, },
"packageManager": "yarn@1.22.17" "packageManager": "pnpm@7.5.0"
} }

View File

@ -1,4 +1,4 @@
COMMERCE_PROVIDER=bigcommerce COMMERCE_PROVIDER=@vercel/commerce-bigcommerce
BIGCOMMERCE_STOREFRONT_API_URL= BIGCOMMERCE_STOREFRONT_API_URL=
BIGCOMMERCE_STOREFRONT_API_TOKEN= BIGCOMMERCE_STOREFRONT_API_TOKEN=

View File

@ -47,17 +47,19 @@
} }
}, },
"dependencies": { "dependencies": {
"@vercel/fetch": "^6.1.1", "@vercel/commerce": "workspace:*",
"@vercel/fetch": "^6.2.0",
"cookie": "^0.4.1", "cookie": "^0.4.1",
"immutability-helper": "^3.1.1", "immutability-helper": "^3.1.1",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"uuidv4": "^6.2.12" "uuidv4": "^6.2.12",
"node-fetch": "^2.6.7"
}, },
"peerDependencies": { "peerDependencies": {
"next": "^12", "next": "^12",
"react": "^17", "react": "^18",
"react-dom": "^17" "react-dom": "^18"
}, },
"devDependencies": { "devDependencies": {
"@taskr/clear": "^1.1.0", "@taskr/clear": "^1.1.0",
@ -67,15 +69,16 @@
"@types/jsonwebtoken": "^8.5.7", "@types/jsonwebtoken": "^8.5.7",
"@types/lodash.debounce": "^4.0.6", "@types/lodash.debounce": "^4.0.6",
"@types/node": "^17.0.8", "@types/node": "^17.0.8",
"@types/react": "^17.0.38", "@types/react": "^18.0.14",
"@types/node-fetch": "^2.6.2",
"lint-staged": "^12.1.7", "lint-staged": "^12.1.7",
"next": "^12.0.8", "next": "^12.0.8",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"react": "^17.0.2", "react": "^18.2.0",
"react-dom": "^17.0.2", "react-dom": "^18.2.0",
"taskr": "^1.1.0", "taskr": "^1.1.0",
"taskr-swc": "^0.0.1", "taskr-swc": "^0.0.1",
"typescript": "^4.5.4" "typescript": "^4.7.4"
}, },
"lint-staged": { "lint-staged": {
"**/*.{js,jsx,ts,tsx,json}": [ "**/*.{js,jsx,ts,tsx,json}": [

View File

@ -1130,7 +1130,7 @@ export interface definitions {
*/ */
search_keywords?: string search_keywords?: string
/** /**
* Image URL used for this category on the storefront. Images can be uploaded via form file post to `/brands/{brandId}/image`, or by providing a publicly accessible URL in this field. * Image URL used for this category on the storefront. Images can be uploaded via form file post to `/{brandId}/image`, or by providing a publicly accessible URL in this field.
*/ */
image_url?: string image_url?: string
custom_url?: definitions['customUrl_Full'] custom_url?: definitions['customUrl_Full']

View File

@ -1,3 +1,4 @@
// @ts-nocheck
import { normalizeCart } from '../../../lib/normalize' import { normalizeCart } from '../../../lib/normalize'
import { parseCartItem } from '../../utils/parse-item' import { parseCartItem } from '../../utils/parse-item'
import getCartCookie from '../../utils/get-cart-cookie' import getCartCookie from '../../utils/get-cart-cookie'

View File

@ -1,7 +1,8 @@
// @ts-nocheck
import { normalizeCart } from '../../../lib/normalize' import { normalizeCart } from '../../../lib/normalize'
import { BigcommerceApiError } from '../../utils/errors' import { BigcommerceApiError } from '../../utils/errors'
import getCartCookie from '../../utils/get-cart-cookie' import getCartCookie from '../../utils/get-cart-cookie'
import type { BigcommerceCart } from '../../../types/cart' import type { BigcommerceCart } from '../../../types'
import type { CartEndpoint } from '.' import type { CartEndpoint } from '.'
// Return current cart info // Return current cart info

View File

@ -1,6 +1,6 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import cartEndpoint from '@vercel/commerce/api/endpoints/cart' import cartEndpoint from '@vercel/commerce/api/endpoints/cart'
import type { CartSchema } from '../../../types/cart' import type { CartSchema } from '@vercel/commerce/types/cart'
import type { BigcommerceAPI } from '../..' import type { BigcommerceAPI } from '../..'
import getCart from './get-cart' import getCart from './get-cart'
import addItem from './add-item' import addItem from './add-item'

View File

@ -1,6 +1,6 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import productsEndpoint from '@vercel/commerce/api/endpoints/catalog/products' import productsEndpoint from '@vercel/commerce/api/endpoints/catalog/products'
import type { ProductsSchema } from '../../../../types/product' import type { ProductsSchema } from '@vercel/commerce/types/product'
import type { BigcommerceAPI } from '../../..' import type { BigcommerceAPI } from '../../..'
import getProducts from './get-products' import getProducts from './get-products'

View File

@ -1,6 +1,6 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import checkoutEndpoint from '@vercel/commerce/api/endpoints/checkout' import checkoutEndpoint from '@vercel/commerce/api/endpoints/checkout'
import type { CheckoutSchema } from '../../../types/checkout' import type { CheckoutSchema } from '@vercel/commerce/types/checkout'
import type { BigcommerceAPI } from '../..' import type { BigcommerceAPI } from '../..'
import getCheckout from './get-checkout' import getCheckout from './get-checkout'

View File

@ -47,7 +47,19 @@ const getLoggedInCustomer: CustomerEndpoint['handlers']['getLoggedInCustomer'] =
}) })
} }
return res.status(200).json({ data: { customer } }) return res.status(200).json({
data: {
customer: {
id: String(customer.entityId),
firstName: customer.firstName,
lastName: customer.lastName,
email: customer.email,
company: customer.company,
phone: customer.phone,
notes: customer.notes,
},
},
})
} }
res.status(200).json({ data: null }) res.status(200).json({ data: null })

View File

@ -1,6 +1,6 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import customerEndpoint from '@vercel/commerce/api/endpoints/customer' import customerEndpoint from '@vercel/commerce/api/endpoints/customer'
import type { CustomerSchema } from '../../../types/customer' import type { CustomerSchema } from '@vercel/commerce/types/customer'
import type { BigcommerceAPI } from '../..' import type { BigcommerceAPI } from '../..'
import getLoggedInCustomer from './get-logged-in-customer' import getLoggedInCustomer from './get-logged-in-customer'

View File

@ -1,6 +1,6 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import loginEndpoint from '@vercel/commerce/api/endpoints/login' import loginEndpoint from '@vercel/commerce/api/endpoints/login'
import type { LoginSchema } from '../../../types/login' import type { LoginSchema } from '@vercel/commerce/types/login'
import type { BigcommerceAPI } from '../..' import type { BigcommerceAPI } from '../..'
import login from './login' import login from './login'

View File

@ -1,6 +1,6 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import logoutEndpoint from '@vercel/commerce/api/endpoints/logout' import logoutEndpoint from '@vercel/commerce/api/endpoints/logout'
import type { LogoutSchema } from '../../../types/logout' import type { LogoutSchema } from '@vercel/commerce/types/logout'
import type { BigcommerceAPI } from '../..' import type { BigcommerceAPI } from '../..'
import logout from './logout' import logout from './logout'

View File

@ -1,6 +1,6 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import signupEndpoint from '@vercel/commerce/api/endpoints/signup' import signupEndpoint from '@vercel/commerce/api/endpoints/signup'
import type { SignupSchema } from '../../../types/signup' import type { SignupSchema } from '@vercel/commerce/types/signup'
import type { BigcommerceAPI } from '../..' import type { BigcommerceAPI } from '../..'
import signup from './signup' import signup from './signup'

View File

@ -3,7 +3,6 @@ import { parseWishlistItem } from '../../utils/parse-item'
import getCustomerId from '../../utils/get-customer-id' import getCustomerId from '../../utils/get-customer-id'
import type { WishlistEndpoint } from '.' import type { WishlistEndpoint } from '.'
// Return wishlist info
const addItem: WishlistEndpoint['handlers']['addItem'] = async ({ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
res, res,
body: { customerToken, item }, body: { customerToken, item },
@ -17,41 +16,52 @@ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
}) })
} }
const customerId = try {
customerToken && (await getCustomerId({ customerToken, config })) const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
if (!customerId) { if (!customerId) {
return res.status(400).json({ throw new Error('Invalid request. No CustomerId')
}
let { wishlist } = await commerce.getCustomerWishlist({
variables: { customerId },
config,
})
if (!wishlist) {
// If user has no wishlist, then let's create one with new item
const { data } = await config.storeApiFetch('/v3/wishlists', {
method: 'POST',
body: JSON.stringify({
name: 'Next.js Commerce Wishlist',
is_public: false,
customer_id: Number(customerId),
items: [parseWishlistItem(item)],
}),
})
return res.status(200).json(data)
}
// Existing Wishlist, let's add Item to Wishlist
const { data } = await config.storeApiFetch(
`/v3/wishlists/${wishlist.id}/items`,
{
method: 'POST',
body: JSON.stringify({
items: [parseWishlistItem(item)],
}),
}
)
// Returns Wishlist
return res.status(200).json(data)
} catch (err: any) {
res.status(500).json({
data: null, data: null,
errors: [{ message: 'Invalid request' }], errors: [{ message: err.message }],
}) })
} }
const { wishlist } = await commerce.getCustomerWishlist({
variables: { customerId },
config,
})
const options = {
method: 'POST',
body: JSON.stringify(
wishlist
? {
items: [parseWishlistItem(item)],
}
: {
name: 'Wishlist',
customer_id: customerId,
items: [parseWishlistItem(item)],
is_public: false,
}
),
}
const { data } = wishlist
? await config.storeApiFetch(`/v3/wishlists/${wishlist.id}/items`, options)
: await config.storeApiFetch('/v3/wishlists', options)
res.status(200).json({ data })
} }
export default addItem export default addItem

View File

@ -1,7 +1,6 @@
import type { Wishlist } from '../../../types/wishlist' import type { Wishlist } from '@vercel/commerce/types/wishlist'
import type { WishlistEndpoint } from '.' import type { WishlistEndpoint } from '.'
import getCustomerId from '../../utils/get-customer-id' import getCustomerId from '../../utils/get-customer-id'
import getCustomerWishlist from '../../operations/get-customer-wishlist'
// Return wishlist info // Return wishlist info
const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({ const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({

View File

@ -1,6 +1,6 @@
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api' import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import wishlistEndpoint from '@vercel/commerce/api/endpoints/wishlist' import wishlistEndpoint from '@vercel/commerce/api/endpoints/wishlist'
import type { WishlistSchema } from '../../../types/wishlist' import type { WishlistSchema } from '@vercel/commerce/types/wishlist'
import type { BigcommerceAPI } from '../..' import type { BigcommerceAPI } from '../..'
import getWishlist from './get-wishlist' import getWishlist from './get-wishlist'
import addItem from './add-item' import addItem from './add-item'

View File

@ -1,5 +1,4 @@
import type { Wishlist } from '../../../types/wishlist' import type { Wishlist } from '@vercel/commerce/types/wishlist'
import getCustomerWishlist from '../../operations/get-customer-wishlist'
import getCustomerId from '../../utils/get-customer-id' import getCustomerId from '../../utils/get-customer-id'
import type { WishlistEndpoint } from '.' import type { WishlistEndpoint } from '.'

View File

@ -38,9 +38,9 @@ export interface BigcommerceConfig extends CommerceAPIConfig {
storeApiFetch<T>(endpoint: string, options?: RequestInit): Promise<T> storeApiFetch<T>(endpoint: string, options?: RequestInit): Promise<T>
} }
const API_URL = process.env.BIGCOMMERCE_STOREFRONT_API_URL const API_URL = process.env.BIGCOMMERCE_STOREFRONT_API_URL // GraphAPI
const API_TOKEN = process.env.BIGCOMMERCE_STOREFRONT_API_TOKEN const API_TOKEN = process.env.BIGCOMMERCE_STOREFRONT_API_TOKEN
const STORE_API_URL = process.env.BIGCOMMERCE_STORE_API_URL const STORE_API_URL = process.env.BIGCOMMERCE_STORE_API_URL // REST API
const STORE_API_TOKEN = process.env.BIGCOMMERCE_STORE_API_TOKEN const STORE_API_TOKEN = process.env.BIGCOMMERCE_STORE_API_TOKEN
const STORE_API_CLIENT_ID = process.env.BIGCOMMERCE_STORE_API_CLIENT_ID const STORE_API_CLIENT_ID = process.env.BIGCOMMERCE_STORE_API_CLIENT_ID
const STORE_CHANNEL_ID = process.env.BIGCOMMERCE_CHANNEL_ID const STORE_CHANNEL_ID = process.env.BIGCOMMERCE_CHANNEL_ID

View File

@ -2,10 +2,13 @@ import type {
OperationContext, OperationContext,
OperationOptions, OperationOptions,
} from '@vercel/commerce/api/operations' } from '@vercel/commerce/api/operations'
import type { Page, GetAllPagesOperation } from '../../types/page' import type { GetAllPagesOperation } from '@vercel/commerce/types/page'
import type { RecursivePartial, RecursiveRequired } from '../utils/types' import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { BigcommerceConfig, Provider } from '..' import { BigcommerceConfig, Provider } from '..'
import { definitions } from '../definitions/store-content'
import { normalizePage } from '../../lib/normalize'
export default function getAllPagesOperation({ export default function getAllPagesOperation({
commerce, commerce,
}: OperationContext<Provider>) { }: OperationContext<Provider>) {
@ -33,12 +36,14 @@ export default function getAllPagesOperation({
// RecursivePartial forces the method to check for every prop in the data, which is // RecursivePartial forces the method to check for every prop in the data, which is
// required in case there's a custom `url` // required in case there's a custom `url`
const { data } = await cfg.storeApiFetch< const { data } = await cfg.storeApiFetch<
RecursivePartial<{ data: Page[] }> RecursivePartial<{ data: definitions['page_Full'][] }>
>('/v3/content/pages') >('/v3/content/pages')
const pages = (data as RecursiveRequired<typeof data>) ?? [] const pages = (data as RecursiveRequired<typeof data>) ?? []
return { return {
pages: preview ? pages : pages.filter((p) => p.is_visible), pages: preview
? pages.map(normalizePage)
: pages.filter((p) => p.is_visible).map(normalizePage),
} }
} }

View File

@ -3,7 +3,7 @@ import type {
OperationOptions, OperationOptions,
} from '@vercel/commerce/api/operations' } from '@vercel/commerce/api/operations'
import type { GetAllProductPathsQuery } from '../../../schema' import type { GetAllProductPathsQuery } from '../../../schema'
import type { GetAllProductPathsOperation } from '../../types/product' import type { GetAllProductPathsOperation } from '@vercel/commerce/types/product'
import type { RecursivePartial, RecursiveRequired } from '../utils/types' import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges' import filterEdges from '../utils/filter-edges'
import { BigcommerceConfig, Provider } from '..' import { BigcommerceConfig, Provider } from '..'

View File

@ -6,7 +6,7 @@ import type {
GetAllProductsQuery, GetAllProductsQuery,
GetAllProductsQueryVariables, GetAllProductsQueryVariables,
} from '../../../schema' } from '../../../schema'
import type { GetAllProductsOperation } from '../../types/product' import type { GetAllProductsOperation } from '@vercel/commerce/types/product'
import type { RecursivePartial, RecursiveRequired } from '../utils/types' import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges' import filterEdges from '../utils/filter-edges'
import setProductLocaleMeta from '../utils/set-product-locale-meta' import setProductLocaleMeta from '../utils/set-product-locale-meta'

View File

@ -5,7 +5,7 @@ import type {
import type { import type {
GetCustomerWishlistOperation, GetCustomerWishlistOperation,
Wishlist, Wishlist,
} from '../../types/wishlist' } from '@vercel/commerce/types/wishlist'
import type { RecursivePartial, RecursiveRequired } from '../utils/types' import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { BigcommerceConfig, Provider } from '..' import { BigcommerceConfig, Provider } from '..'
import getAllProducts, { ProductEdge } from './get-all-products' import getAllProducts, { ProductEdge } from './get-all-products'
@ -44,11 +44,12 @@ export default function getCustomerWishlistOperation({
const { data = [] } = await config.storeApiFetch< const { data = [] } = await config.storeApiFetch<
RecursivePartial<{ data: Wishlist[] }> RecursivePartial<{ data: Wishlist[] }>
>(`/v3/wishlists?customer_id=${variables.customerId}`) >(`/v3/wishlists?customer_id=${variables.customerId}`)
const wishlist = data[0] const wishlist = data[0]
if (includeProducts && wishlist?.items?.length) { if (includeProducts && wishlist?.items?.length) {
const ids = wishlist.items const ids = wishlist.items
?.map((item) => (item?.product_id ? String(item?.product_id) : null)) ?.map((item) => (item?.productId ? String(item?.productId) : null))
.filter((id): id is string => !!id) .filter((id): id is string => !!id)
if (ids?.length) { if (ids?.length) {
@ -65,7 +66,7 @@ export default function getCustomerWishlistOperation({
}, {}) }, {})
// Populate the wishlist items with the graphql products // Populate the wishlist items with the graphql products
wishlist.items.forEach((item) => { wishlist.items.forEach((item) => {
const product = item && productsById[item.product_id!] const product = item && productsById[Number(item.productId)]
if (item && product) { if (item && product) {
// @ts-ignore Fix this type when the wishlist type is properly defined // @ts-ignore Fix this type when the wishlist type is properly defined
item.product = product item.product = product

View File

@ -2,7 +2,7 @@ import type {
OperationContext, OperationContext,
OperationOptions, OperationOptions,
} from '@vercel/commerce/api/operations' } from '@vercel/commerce/api/operations'
import type { GetPageOperation, Page } from '../../types/page' import type { GetPageOperation, Page } from '@vercel/commerce/types/page'
import type { RecursivePartial, RecursiveRequired } from '../utils/types' import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import type { BigcommerceConfig, Provider } from '..' import type { BigcommerceConfig, Provider } from '..'
import { normalizePage } from '../../lib/normalize' import { normalizePage } from '../../lib/normalize'

View File

@ -2,7 +2,7 @@ import type {
OperationContext, OperationContext,
OperationOptions, OperationOptions,
} from '@vercel/commerce/api/operations' } from '@vercel/commerce/api/operations'
import type { GetProductOperation } from '../../types/product' import type { GetProductOperation } from '@vercel/commerce/types/product'
import type { GetProductQuery, GetProductQueryVariables } from '../../../schema' import type { GetProductQuery, GetProductQueryVariables } from '../../../schema'
import setProductLocaleMeta from '../utils/set-product-locale-meta' import setProductLocaleMeta from '../utils/set-product-locale-meta'
import { productInfoFragment } from '../fragments/product' import { productInfoFragment } from '../fragments/product'
@ -100,7 +100,7 @@ export default function getAllProductPathsOperation({
const variables: GetProductQueryVariables = { const variables: GetProductQueryVariables = {
locale, locale,
hasLocale: !!locale, hasLocale: !!locale,
path: slug ? `/${slug}/` : vars.path!, path: slug ? `/${slug}` : vars.path!,
} }
const { data } = await config.fetch<GetProductQuery>(query, { variables }) const { data } = await config.fetch<GetProductQuery>(query, { variables })
const product = data.site?.route?.node const product = data.site?.route?.node

View File

@ -2,12 +2,12 @@ import type {
OperationContext, OperationContext,
OperationOptions, OperationOptions,
} from '@vercel/commerce/api/operations' } from '@vercel/commerce/api/operations'
import type { GetSiteInfoOperation } from '../../types/site' import type { GetSiteInfoOperation } from '@vercel/commerce/types/site'
import type { GetSiteInfoQuery } from '../../../schema' import type { GetSiteInfoQuery } from '../../../schema'
import filterEdges from '../utils/filter-edges' import filterEdges from '../utils/filter-edges'
import type { BigcommerceConfig, Provider } from '..' import type { BigcommerceConfig, Provider } from '..'
import { categoryTreeItemFragment } from '../fragments/category-tree' import { categoryTreeItemFragment } from '../fragments/category-tree'
import { normalizeCategory } from '../../lib/normalize' import { normalizeBrand, normalizeCategory } from '../../lib/normalize'
// Get 3 levels of categories // Get 3 levels of categories
export const getSiteInfoQuery = /* GraphQL */ ` export const getSiteInfoQuery = /* GraphQL */ `
@ -79,7 +79,7 @@ export default function getSiteInfoOperation({
return { return {
categories: categories ?? [], categories: categories ?? [],
brands: filterEdges(brands), brands: filterEdges(brands).map(normalizeBrand),
} }
} }

View File

@ -3,7 +3,7 @@ import type {
OperationContext, OperationContext,
OperationOptions, OperationOptions,
} from '@vercel/commerce/api/operations' } from '@vercel/commerce/api/operations'
import type { LoginOperation } from '../../types/login' import type { LoginOperation } from '@vercel/commerce/types/login'
import type { LoginMutation } from '../../../schema' import type { LoginMutation } from '../../../schema'
import type { RecursivePartial } from '../utils/types' import type { RecursivePartial } from '../utils/types'
import concatHeader from '../utils/concat-cookie' import concatHeader from '../utils/concat-cookie'

View File

@ -1,5 +1,5 @@
import type { WishlistItemBody } from '../../types/wishlist' import type { WishlistItemBody } from '@vercel/commerce/types/wishlist'
import type { CartItemBody, OptionSelections } from '../../types/cart' import type { CartItemBody, SelectedOption } from '@vercel/commerce/types/cart'
type BCWishlistItemBody = { type BCWishlistItemBody = {
product_id: number product_id: number
@ -10,7 +10,7 @@ type BCCartItemBody = {
product_id: number product_id: number
variant_id: number variant_id: number
quantity?: number quantity?: number
option_selections?: OptionSelections[] option_selections?: SelectedOption[]
} }
export const parseWishlistItem = ( export const parseWishlistItem = (
@ -24,5 +24,5 @@ export const parseCartItem = (item: CartItemBody): BCCartItemBody => ({
quantity: item.quantity, quantity: item.quantity,
product_id: Number(item.productId), product_id: Number(item.productId),
variant_id: Number(item.variantId), variant_id: Number(item.variantId),
option_selections: item.optionSelections, option_selections: item.optionsSelected,
}) })

View File

@ -2,7 +2,7 @@ import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types' import type { MutationHook } from '@vercel/commerce/utils/types'
import { CommerceError } from '@vercel/commerce/utils/errors' import { CommerceError } from '@vercel/commerce/utils/errors'
import useLogin, { UseLogin } from '@vercel/commerce/auth/use-login' import useLogin, { UseLogin } from '@vercel/commerce/auth/use-login'
import type { LoginHook } from '../types/login' import type { LoginHook } from '@vercel/commerce/types/login'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
export default useLogin as UseLogin<typeof handler> export default useLogin as UseLogin<typeof handler>

View File

@ -1,7 +1,7 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types' import type { MutationHook } from '@vercel/commerce/utils/types'
import useLogout, { UseLogout } from '@vercel/commerce/auth/use-logout' import useLogout, { UseLogout } from '@vercel/commerce/auth/use-logout'
import type { LogoutHook } from '../types/logout' import type { LogoutHook } from '@vercel/commerce/types/logout'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
export default useLogout as UseLogout<typeof handler> export default useLogout as UseLogout<typeof handler>

View File

@ -1,8 +1,8 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types' import type { MutationHook } from '@vercel/commerce/utils/types'
import { CommerceError } from '@vercel/commerce/utils/errors' import { CommerceError } from '@vercel/commerce/utils/errors'
import useSignup, { UseSignup } from '@vercel/commerce/auth/use-signup' import useSignup, { type UseSignup } from '@vercel/commerce/auth/use-signup'
import type { SignupHook } from '../types/signup' import type { SignupHook } from '@vercel/commerce/types/signup'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
export default useSignup as UseSignup<typeof handler> export default useSignup as UseSignup<typeof handler>

View File

@ -4,8 +4,14 @@ import type {
HookFetcherContext, HookFetcherContext,
} from '@vercel/commerce/utils/types' } from '@vercel/commerce/utils/types'
import { ValidationError } from '@vercel/commerce/utils/errors' import { ValidationError } from '@vercel/commerce/utils/errors'
import useRemoveItem, { UseRemoveItem } from '@vercel/commerce/cart/use-remove-item' import useRemoveItem, {
import type { Cart, LineItem, RemoveItemHook } from '@vercel/commerce/types/cart' UseRemoveItem,
} from '@vercel/commerce/cart/use-remove-item'
import type {
Cart,
LineItem,
RemoveItemHook,
} from '@vercel/commerce/types/cart'
import useCart from './use-cart' import useCart from './use-cart'
export type RemoveItemFn<T = any> = T extends LineItem export type RemoveItemFn<T = any> = T extends LineItem

View File

@ -1,6 +1,8 @@
import { SWRHook } from '@vercel/commerce/utils/types' import type { SWRHook } from '@vercel/commerce/utils/types'
import useCustomer, { UseCustomer } from '@vercel/commerce/customer/use-customer' import useCustomer, {
import type { CustomerHook } from '../types/customer' type UseCustomer,
} from '@vercel/commerce/customer/use-customer'
import type { CustomerHook } from '@vercel/commerce/types/customer'
export default useCustomer as UseCustomer<typeof handler> export default useCustomer as UseCustomer<typeof handler>

View File

@ -1,7 +1,9 @@
import type { Product } from '../types/product' import type { Page } from '@vercel/commerce/types/page'
import type { Cart, BigcommerceCart, LineItem } from '../types/cart' import type { Product } from '@vercel/commerce/types/product'
import type { Page } from '../types/page' import type { Cart, LineItem } from '@vercel/commerce/types/cart'
import type { BCCategory, Category } from '../types/site' import type { Category, Brand } from '@vercel/commerce/types/site'
import type { BigcommerceCart, BCCategory, BCBrand } from '../types'
import { definitions } from '../api/definitions/store-content' import { definitions } from '../api/definitions/store-content'
import update from './immutability' import update from './immutability'
import getSlug from './get-slug' import getSlug from './get-slug'
@ -12,7 +14,7 @@ function normalizeProductOption(productOption: any) {
} = productOption } = productOption
return { return {
id: entityId, id: String(entityId),
values: edges?.map(({ node }: any) => node), values: edges?.map(({ node }: any) => node),
...rest, ...rest,
} }
@ -41,7 +43,7 @@ export function normalizeProduct(productNode: any): Product {
variants: { variants: {
$apply: ({ edges }: any) => $apply: ({ edges }: any) =>
edges?.map(({ node: { entityId, productOptions, ...rest } }: any) => ({ edges?.map(({ node: { entityId, productOptions, ...rest } }: any) => ({
id: entityId, id: String(entityId),
options: productOptions?.edges options: productOptions?.edges
? productOptions.edges.map(normalizeProductOption) ? productOptions.edges.map(normalizeProductOption)
: [], : [],
@ -54,7 +56,7 @@ export function normalizeProduct(productNode: any): Product {
: [], : [],
}, },
brand: { brand: {
$apply: (brand: any) => (brand?.entityId ? brand?.entityId : null), $apply: (brand: any) => (brand?.id ? brand.id : null),
}, },
slug: { slug: {
$set: path?.replace(/^\/+|\/+$/g, ''), $set: path?.replace(/^\/+|\/+$/g, ''),
@ -75,7 +77,8 @@ export function normalizePage(page: definitions['page_Full']): Page {
name: page.name, name: page.name,
is_visible: page.is_visible, is_visible: page.is_visible,
sort_order: page.sort_order, sort_order: page.sort_order,
body: page.body, body: page.body ?? '',
url: page.url,
} }
} }
@ -134,3 +137,14 @@ export function normalizeCategory(category: BCCategory): Category {
path: category.path, path: category.path,
} }
} }
export function normalizeBrand(brand: BCBrand): Brand {
const path = brand.node.path.replace('/brands/', '')
const slug = getSlug(path)
return {
id: `${brand.node.entityId}`,
name: brand.node.name,
slug,
path: `/${slug}`,
}
}

View File

@ -1,13 +1,13 @@
import { SWRHook } from '@vercel/commerce/utils/types' import { SWRHook } from '@vercel/commerce/utils/types'
import useSearch, { UseSearch } from '@vercel/commerce/product/use-search' import useSearch, { UseSearch } from '@vercel/commerce/product/use-search'
import type { SearchProductsHook } from '../types/product' import type { SearchProductsHook } from '@vercel/commerce/types/product'
export default useSearch as UseSearch<typeof handler> export default useSearch as UseSearch<typeof handler>
export type SearchProductsInput = { export type SearchProductsInput = {
search?: string search?: string
categoryId?: number | string categoryId?: string
brandId?: number brandId?: string
sort?: string sort?: string
locale?: string locale?: string
} }
@ -24,7 +24,7 @@ export const handler: SWRHook<SearchProductsHook> = {
if (search) url.searchParams.set('search', search) if (search) url.searchParams.set('search', search)
if (Number.isInteger(Number(categoryId))) if (Number.isInteger(Number(categoryId)))
url.searchParams.set('categoryId', String(categoryId)) url.searchParams.set('categoryId', String(categoryId))
if (Number.isInteger(brandId)) if (Number.isInteger(Number(brandId)))
url.searchParams.set('brandId', String(brandId)) url.searchParams.set('brandId', String(brandId))
if (sort) url.searchParams.set('sort', sort) if (sort) url.searchParams.set('sort', sort)

View File

@ -0,0 +1,32 @@
import type { GetSiteInfoQuery } from '../schema'
export type BCCategory = NonNullable<
GetSiteInfoQuery['site']['categoryTree']
>[0]
export type BCBrand = NonNullable<
NonNullable<GetSiteInfoQuery['site']['brands']['edges']>[0]
>
// TODO: this type should match:
// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses
export type BigcommerceCart = {
id: string
parent_id?: string
customer_id: number
email: string
currency: { code: string }
tax_included: boolean
base_amount: number
discount_amount: number
cart_amount: number
line_items: {
custom_items: any[]
digital_items: any[]
gift_certificates: any[]
physical_items: any[]
}
created_time: string
discounts?: { id: number; discounted_amount: number }[]
// TODO: add missing fields
}

View File

@ -1,66 +0,0 @@
import * as Core from '@vercel/commerce/types/cart'
export * from '@vercel/commerce/types/cart'
// TODO: this type should match:
// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses
export type BigcommerceCart = {
id: string
parent_id?: string
customer_id: number
email: string
currency: { code: string }
tax_included: boolean
base_amount: number
discount_amount: number
cart_amount: number
line_items: {
custom_items: any[]
digital_items: any[]
gift_certificates: any[]
physical_items: any[]
}
created_time: string
discounts?: { id: number; discounted_amount: number }[]
// TODO: add missing fields
}
/**
* Extend core cart types
*/
export type Cart = Core.Cart & {
lineItems: Core.LineItem[]
}
export type OptionSelections = {
option_id: number
option_value: number | string
}
export type CartItemBody = Core.CartItemBody & {
productId: string // The product id is always required for BC
optionSelections?: OptionSelections[]
}
export type CartTypes = {
cart: Cart
item: Core.LineItem
itemBody: CartItemBody
}
export type CartHooks = Core.CartHooks<CartTypes>
export type GetCartHook = CartHooks['getCart']
export type AddItemHook = CartHooks['addItem']
export type UpdateItemHook = CartHooks['updateItem']
export type RemoveItemHook = CartHooks['removeItem']
export type CartSchema = Core.CartSchema<CartTypes>
export type CartHandlers = Core.CartHandlers<CartTypes>
export type GetCartHandler = CartHandlers['getCart']
export type AddItemHandler = CartHandlers['addItem']
export type UpdateItemHandler = CartHandlers['updateItem']
export type RemoveItemHandler = CartHandlers['removeItem']

View File

@ -1 +0,0 @@
export * from '@vercel/commerce/types/checkout'

View File

@ -1 +0,0 @@
export * from '@vercel/commerce/types/common'

View File

@ -1,5 +0,0 @@
import * as Core from '@vercel/commerce/types/customer'
export * from '@vercel/commerce/types/customer'
export type CustomerSchema = Core.CustomerSchema

View File

@ -1,25 +0,0 @@
import * as Cart from './cart'
import * as Checkout from './checkout'
import * as Common from './common'
import * as Customer from './customer'
import * as Login from './login'
import * as Logout from './logout'
import * as Page from './page'
import * as Product from './product'
import * as Signup from './signup'
import * as Site from './site'
import * as Wishlist from './wishlist'
export type {
Cart,
Checkout,
Common,
Customer,
Login,
Logout,
Page,
Product,
Signup,
Site,
Wishlist,
}

View File

@ -1,8 +0,0 @@
import * as Core from '@vercel/commerce/types/login'
import type { LoginMutationVariables } from '../../schema'
export * from '@vercel/commerce/types/login'
export type LoginOperation = Core.LoginOperation & {
variables: LoginMutationVariables
}

View File

@ -1 +0,0 @@
export * from '@vercel/commerce/types/logout'

View File

@ -1,11 +0,0 @@
import * as Core from '@vercel/commerce/types/page'
export * from '@vercel/commerce/types/page'
export type Page = Core.Page
export type PageTypes = {
page: Page
}
export type GetAllPagesOperation = Core.GetAllPagesOperation<PageTypes>
export type GetPageOperation = Core.GetPageOperation<PageTypes>

View File

@ -1 +0,0 @@
export * from '@vercel/commerce/types/product'

View File

@ -1 +0,0 @@
export * from '@vercel/commerce/types/signup'

View File

@ -1,19 +0,0 @@
import * as Core from '@vercel/commerce/types/site'
import type { GetSiteInfoQuery, GetSiteInfoQueryVariables } from '../../schema'
export * from '@vercel/commerce/types/site'
export type BCCategory = NonNullable<
GetSiteInfoQuery['site']['categoryTree']
>[0]
export type Brand = NonNullable<
NonNullable<GetSiteInfoQuery['site']['brands']['edges']>[0]
>
export type SiteTypes = {
category: Core.Category
brand: Brand
}
export type GetSiteInfoOperation = Core.GetSiteInfoOperation<SiteTypes>

View File

@ -1,24 +0,0 @@
import * as Core from '@vercel/commerce/types/wishlist'
import { definitions } from '../api/definitions/wishlist'
import type { ProductEdge } from '../api/operations/get-all-products'
export * from '@vercel/commerce/types/wishlist'
export type WishlistItem = NonNullable<
definitions['wishlist_Full']['items']
>[0] & {
product?: ProductEdge['node']
}
export type Wishlist = Omit<definitions['wishlist_Full'], 'items'> & {
items?: WishlistItem[]
}
export type WishlistTypes = {
wishlist: Wishlist
itemBody: Core.WishlistItemBody
}
export type WishlistSchema = Core.WishlistSchema<WishlistTypes>
export type GetCustomerWishlistOperation =
Core.GetCustomerWishlistOperation<WishlistTypes>

View File

@ -1,8 +1,10 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types' import type { MutationHook } from '@vercel/commerce/utils/types'
import { CommerceError } from '@vercel/commerce/utils/errors' import { CommerceError } from '@vercel/commerce/utils/errors'
import useAddItem, { UseAddItem } from '@vercel/commerce/wishlist/use-add-item' import useAddItem, {
import type { AddItemHook } from '../types/wishlist' type UseAddItem,
} from '@vercel/commerce/wishlist/use-add-item'
import type { AddItemHook } from '@vercel/commerce/types/wishlist'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import useWishlist from './use-wishlist' import useWishlist from './use-wishlist'

View File

@ -2,9 +2,9 @@ import { useCallback } from 'react'
import type { MutationHook } from '@vercel/commerce/utils/types' import type { MutationHook } from '@vercel/commerce/utils/types'
import { CommerceError } from '@vercel/commerce/utils/errors' import { CommerceError } from '@vercel/commerce/utils/errors'
import useRemoveItem, { import useRemoveItem, {
UseRemoveItem, type UseRemoveItem,
} from '@vercel/commerce/wishlist/use-remove-item' } from '@vercel/commerce/wishlist/use-remove-item'
import type { RemoveItemHook } from '../types/wishlist' import type { RemoveItemHook } from '@vercel/commerce/types/wishlist'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import useWishlist from './use-wishlist' import useWishlist from './use-wishlist'

View File

@ -1,11 +1,13 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { SWRHook } from '@vercel/commerce/utils/types' import { SWRHook } from '@vercel/commerce/utils/types'
import useWishlist, { UseWishlist } from '@vercel/commerce/wishlist/use-wishlist' import useWishlist, {
import type { GetWishlistHook } from '../types/wishlist' type UseWishlist,
} from '@vercel/commerce/wishlist/use-wishlist'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
export default useWishlist as UseWishlist<typeof handler> import type { GetWishlistHook } from '@vercel/commerce/types/wishlist'
export default useWishlist as UseWishlist<typeof handler>
export const handler: SWRHook<GetWishlistHook> = { export const handler: SWRHook<GetWishlistHook> = {
fetchOptions: { fetchOptions: {
url: '/api/wishlist', url: '/api/wishlist',
@ -30,7 +32,7 @@ export const handler: SWRHook<GetWishlistHook> = {
const { data: customer } = useCustomer() const { data: customer } = useCustomer()
const response = useData({ const response = useData({
input: [ input: [
['customerId', customer?.entityId], ['customerId', customer?.id],
['includeProducts', input?.includeProducts], ['includeProducts', input?.includeProducts],
], ],
swrOptions: { swrOptions: {

View File

@ -14,6 +14,7 @@ A commerce provider is a headless e-commerce platform that integrates with the [
- Spree ([packages/spree](../spree)) - Spree ([packages/spree](../spree))
- Kibo Commerce ([packages/kibocommerce](../kibocommerce)) - Kibo Commerce ([packages/kibocommerce](../kibocommerce))
- Commerce.js ([packages/commercejs](../commercejs)) - Commerce.js ([packages/commercejs](../commercejs))
- SFCC - SalesForce Cloud Commerce ([packages/sfcc](../sfcc))
Adding a commerce provider means adding a new folder in `packages` with a folder structure like the next one: Adding a commerce provider means adding a new folder in `packages` with a folder structure like the next one:

View File

@ -47,16 +47,18 @@
} }
}, },
"dependencies": { "dependencies": {
"@vercel/fetch": "^6.1.1", "@vercel/fetch": "^6.2.0",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"import-cwd": "^3.0.0", "import-cwd": "^3.0.0",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"swr": "^1.2.0" "swr": "^1.3.0",
"node-fetch": "^2.6.7",
"zod": "^3.19.0"
}, },
"peerDependencies": { "peerDependencies": {
"next": "^12", "next": "^12",
"react": "^17", "react": "^18",
"react-dom": "^17" "react-dom": "^18"
}, },
"devDependencies": { "devDependencies": {
"@taskr/clear": "^1.1.0", "@taskr/clear": "^1.1.0",
@ -64,15 +66,16 @@
"@taskr/watch": "^1.1.0", "@taskr/watch": "^1.1.0",
"@types/js-cookie": "^3.0.1", "@types/js-cookie": "^3.0.1",
"@types/node": "^17.0.8", "@types/node": "^17.0.8",
"@types/react": "^17.0.38", "@types/react": "^18.0.14",
"@types/node-fetch": "2.6.2",
"lint-staged": "^12.1.7", "lint-staged": "^12.1.7",
"next": "^12.0.8", "next": "^12.0.8",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"react": "^17.0.2", "react": "^18.2.0",
"react-dom": "^17.0.2", "react-dom": "^18.2.0",
"taskr": "^1.1.0", "taskr": "^1.1.0",
"taskr-swc": "^0.0.1", "taskr-swc": "^0.0.1",
"typescript": "^4.5.4" "typescript": "^4.7.4"
}, },
"lint-staged": { "lint-staged": {
"**/*.{js,jsx,ts,tsx,json}": [ "**/*.{js,jsx,ts,tsx,json}": [

View File

@ -3,58 +3,60 @@ import { CommerceAPIError } from '../utils/errors'
import isAllowedOperation from '../utils/is-allowed-operation' import isAllowedOperation from '../utils/is-allowed-operation'
import type { GetAPISchema } from '..' import type { GetAPISchema } from '..'
const cartEndpoint: GetAPISchema<any, CartSchema<any>>['endpoint']['handler'] = const cartEndpoint: GetAPISchema<
async (ctx) => { any,
const { req, res, handlers, config } = ctx CartSchema
>['endpoint']['handler'] = async (ctx) => {
const { req, res, handlers, config } = ctx
if ( if (
!isAllowedOperation(req, res, { !isAllowedOperation(req, res, {
GET: handlers['getCart'], GET: handlers['getCart'],
POST: handlers['addItem'], POST: handlers['addItem'],
PUT: handlers['updateItem'], PUT: handlers['updateItem'],
DELETE: handlers['removeItem'], DELETE: handlers['removeItem'],
}) })
) { ) {
return return
}
const { cookies } = req
const cartId = cookies[config.cartCookie]
try {
// Return current cart info
if (req.method === 'GET') {
const body = { cartId }
return await handlers['getCart']({ ...ctx, body })
}
// Create or add an item to the cart
if (req.method === 'POST') {
const body = { ...req.body, cartId }
return await handlers['addItem']({ ...ctx, body })
}
// Update item in cart
if (req.method === 'PUT') {
const body = { ...req.body, cartId }
return await handlers['updateItem']({ ...ctx, body })
}
// Remove an item from the cart
if (req.method === 'DELETE') {
const body = { ...req.body, cartId }
return await handlers['removeItem']({ ...ctx, body })
}
} catch (error) {
console.error(error)
const message =
error instanceof CommerceAPIError
? 'An unexpected error ocurred with the Commerce API'
: 'An unexpected error ocurred'
res.status(500).json({ data: null, errors: [{ message }] })
}
} }
const { cookies } = req
const cartId = cookies[config.cartCookie]
try {
// Return current cart info
if (req.method === 'GET') {
const body = { cartId }
return await handlers['getCart']({ ...ctx, body })
}
// Create or add an item to the cart
if (req.method === 'POST') {
const body = { ...req.body, cartId }
return await handlers['addItem']({ ...ctx, body })
}
// Update item in cart
if (req.method === 'PUT') {
const body = { ...req.body, cartId }
return await handlers['updateItem']({ ...ctx, body })
}
// Remove an item from the cart
if (req.method === 'DELETE') {
const body = { ...req.body, cartId }
return await handlers['removeItem']({ ...ctx, body })
}
} catch (error) {
console.error(error)
const message =
error instanceof CommerceAPIError
? 'An unexpected error ocurred with the Commerce API'
: 'An unexpected error ocurred'
res.status(500).json({ data: null, errors: [{ message }] })
}
}
export default cartEndpoint export default cartEndpoint

View File

@ -6,7 +6,7 @@ import isAllowedOperation from '../../utils/is-allowed-operation'
const customerEndpoint: GetAPISchema< const customerEndpoint: GetAPISchema<
any, any,
CustomerSchema<any> CustomerSchema
>['endpoint']['handler'] = async (ctx) => { >['endpoint']['handler'] = async (ctx) => {
const { req, res, handlers } = ctx const { req, res, handlers } = ctx

View File

@ -5,7 +5,7 @@ import type { GetAPISchema } from '..'
const loginEndpoint: GetAPISchema< const loginEndpoint: GetAPISchema<
any, any,
LoginSchema<any> LoginSchema
>['endpoint']['handler'] = async (ctx) => { >['endpoint']['handler'] = async (ctx) => {
const { req, res, handlers } = ctx const { req, res, handlers } = ctx

View File

@ -5,7 +5,7 @@ import type { GetAPISchema } from '..'
const wishlistEndpoint: GetAPISchema< const wishlistEndpoint: GetAPISchema<
any, any,
WishlistSchema<any> WishlistSchema
>['endpoint']['handler'] = async (ctx) => { >['endpoint']['handler'] = async (ctx) => {
const { req, res, handlers, config } = ctx const { req, res, handlers, config } = ctx

View File

@ -11,11 +11,14 @@ import type { WishlistSchema } from '../types/wishlist'
import type { CheckoutSchema } from '../types/checkout' import type { CheckoutSchema } from '../types/checkout'
import type { CustomerCardSchema } from '../types/customer/card' import type { CustomerCardSchema } from '../types/customer/card'
import type { CustomerAddressSchema } from '../types/customer/address' import type { CustomerAddressSchema } from '../types/customer/address'
import { withOperationCallback } from './utils/with-operation-callback'
import { import {
defaultOperations,
OPERATIONS, OPERATIONS,
AllOperations, AllOperations,
APIOperations, APIOperations,
defaultOperations,
} from './operations' } from './operations'
export type APISchemas = export type APISchemas =
@ -106,7 +109,10 @@ export function getCommerceApi<P extends APIProvider>(
OPERATIONS.forEach((k) => { OPERATIONS.forEach((k) => {
const op = ops[k] const op = ops[k]
if (op) { if (op) {
commerce[k] = op({ commerce }) as AllOperations<P>[typeof k] commerce[k] = withOperationCallback(
k,
op({ commerce })
) as AllOperations<P>[typeof k]
} }
}) })

View File

@ -25,6 +25,13 @@ export const OPERATIONS = [
'getProduct', 'getProduct',
] as const ] as const
export type Operation = {
[O in AllowedOperations]: {
name: O
data: Awaited<ReturnType<Operations<APIProvider>[O]>>
}
}[AllowedOperations]
export const defaultOperations = OPERATIONS.reduce((ops, k) => { export const defaultOperations = OPERATIONS.reduce((ops, k) => {
ops[k] = noop ops[k] = noop
return ops return ops

View File

@ -1,4 +1,7 @@
import { ZodError } from 'zod'
import type { Response } from '@vercel/fetch' import type { Response } from '@vercel/fetch'
import { CommerceError } from '../../utils/errors'
export class CommerceAPIError extends Error { export class CommerceAPIError extends Error {
status: number status: number
@ -20,3 +23,25 @@ export class CommerceNetworkError extends Error {
this.name = 'CommerceNetworkError' this.name = 'CommerceNetworkError'
} }
} }
export const getOperationError = (operation: string, error: unknown) => {
if (error instanceof ZodError) {
return new CommerceError({
code: 'SCHEMA_VALIDATION_ERROR',
message:
`The ${operation} operation returned invalid data and has ${
error.issues.length
} parse ${error.issues.length === 1 ? 'error' : 'errors'}: \n` +
error.issues
.map(
(e, index) =>
`Error #${index + 1} ${
e.path.length > 0 ? `Path: ${e.path.join('.')}, ` : ''
}Code: ${e.code}, Message: ${e.message}`
)
.join('\n'),
})
}
return error
}

View File

@ -0,0 +1,42 @@
import type { AllowedOperations, Operation } from '../operations'
import { z } from 'zod'
import { getOperationError } from './errors'
import { pageSchema } from '../../schemas/page'
import { siteInfoSchema } from '../../schemas/site'
import { productSchema, productsPathsSchema } from '../../schemas/product'
export const withOperationCallback =
(name: AllowedOperations, fn: (...args: any[]) => Promise<any>) =>
async (...args: any[]) => {
try {
const data = await fn(...args)
parse({ name, data })
return data
} catch (error) {
throw getOperationError(name, error)
}
}
const parse = ({ name, data }: Operation) => {
switch (name) {
case 'getProduct':
productSchema.nullable().parse(data.product)
break
case 'getAllProducts':
z.array(productSchema).parse(data.products)
break
case 'getAllProductPaths':
productsPathsSchema.parse(data.products)
break
case 'getPage':
pageSchema.nullable().parse(data.page)
break
case 'getAllPages':
z.array(pageSchema).parse(data.pages)
break
case 'getSiteInfo':
siteInfoSchema.parse(data)
break
}
}

View File

@ -5,7 +5,7 @@ import type { LoginHook } from '../types/login'
import type { Provider } from '..' import type { Provider } from '..'
export type UseLogin< export type UseLogin<
H extends MutationHook<LoginHook<any>> = MutationHook<LoginHook> H extends MutationHook<LoginHook> = MutationHook<LoginHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<LoginHook> = mutationFetcher export const fetcher: HookFetcherFn<LoginHook> = mutationFetcher

View File

@ -5,7 +5,7 @@ import type { LogoutHook } from '../types/logout'
import type { Provider } from '..' import type { Provider } from '..'
export type UseLogout< export type UseLogout<
H extends MutationHook<LogoutHook<any>> = MutationHook<LogoutHook> H extends MutationHook<LogoutHook> = MutationHook<LogoutHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<LogoutHook> = mutationFetcher export const fetcher: HookFetcherFn<LogoutHook> = mutationFetcher

View File

@ -5,7 +5,7 @@ import type { SignupHook } from '../types/signup'
import type { Provider } from '..' import type { Provider } from '..'
export type UseSignup< export type UseSignup<
H extends MutationHook<SignupHook<any>> = MutationHook<SignupHook> H extends MutationHook<SignupHook> = MutationHook<SignupHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<SignupHook> = mutationFetcher export const fetcher: HookFetcherFn<SignupHook> = mutationFetcher

View File

@ -5,7 +5,7 @@ import type { AddItemHook } from '../types/cart'
import type { Provider } from '..' import type { Provider } from '..'
export type UseAddItem< export type UseAddItem<
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook> H extends MutationHook<AddItemHook> = MutationHook<AddItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher

View File

@ -4,9 +4,8 @@ import type { SWRHook, HookFetcherFn } from '../utils/types'
import type { GetCartHook } from '../types/cart' import type { GetCartHook } from '../types/cart'
import { Provider, useCommerce } from '..' import { Provider, useCommerce } from '..'
export type UseCart< export type UseCart<H extends SWRHook<GetCartHook> = SWRHook<GetCartHook>> =
H extends SWRHook<GetCartHook<any>> = SWRHook<GetCartHook> ReturnType<H['useHook']>
> = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<GetCartHook> = async ({ export const fetcher: HookFetcherFn<GetCartHook> = async ({
options, options,

View File

@ -5,7 +5,7 @@ import type { RemoveItemHook } from '../types/cart'
import type { Provider } from '..' import type { Provider } from '..'
export type UseRemoveItem< export type UseRemoveItem<
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook> H extends MutationHook<RemoveItemHook> = MutationHook<RemoveItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher

View File

@ -5,7 +5,7 @@ import type { UpdateItemHook } from '../types/cart'
import type { Provider } from '..' import type { Provider } from '..'
export type UseUpdateItem< export type UseUpdateItem<
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook> H extends MutationHook<UpdateItemHook> = MutationHook<UpdateItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher

View File

@ -7,7 +7,7 @@ import { useHook, useSWRHook } from '../utils/use-hook'
import { Provider, useCommerce } from '..' import { Provider, useCommerce } from '..'
export type UseCheckout< export type UseCheckout<
H extends SWRHook<GetCheckoutHook<any>> = SWRHook<GetCheckoutHook> H extends SWRHook<GetCheckoutHook> = SWRHook<GetCheckoutHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<GetCheckoutHook> = async ({ export const fetcher: HookFetcherFn<GetCheckoutHook> = async ({

View File

@ -6,9 +6,7 @@ import { useHook, useMutationHook } from '../utils/use-hook'
import { mutationFetcher } from '../utils/default-fetcher' import { mutationFetcher } from '../utils/default-fetcher'
export type UseSubmitCheckout< export type UseSubmitCheckout<
H extends MutationHook< H extends MutationHook<SubmitCheckoutHook> = MutationHook<SubmitCheckoutHook>
SubmitCheckoutHook<any>
> = MutationHook<SubmitCheckoutHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<SubmitCheckoutHook> = mutationFetcher export const fetcher: HookFetcherFn<SubmitCheckoutHook> = mutationFetcher

View File

@ -16,12 +16,16 @@ function withCommerceConfig(nextConfig = {}) {
) )
} }
const commerceNextConfig = importCwd(path.join(provider, 'next.config')) const commerceNextConfig = importCwd(path.posix.join(provider, 'next.config'))
const config = merge(nextConfig, commerceNextConfig) const config = merge(nextConfig, commerceNextConfig)
const features = merge(
config.commerce.features,
config.commerce[provider]?.features ?? {}
)
config.env = config.env || {} config.env = config.env || {}
Object.entries(config.commerce.features).forEach(([k, v]) => { Object.entries(features).forEach(([k, v]) => {
if (v) config.env[`COMMERCE_${k.toUpperCase()}_ENABLED`] = true if (v) config.env[`COMMERCE_${k.toUpperCase()}_ENABLED`] = true
}) })

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseAddItem< export type UseAddItem<
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook> H extends MutationHook<AddItemHook> = MutationHook<AddItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher

View File

@ -7,7 +7,7 @@ import { useHook, useSWRHook } from '../../utils/use-hook'
import { Provider, useCommerce } from '../..' import { Provider, useCommerce } from '../..'
export type UseAddresses< export type UseAddresses<
H extends SWRHook<GetAddressesHook<any>> = SWRHook<GetAddressesHook> H extends SWRHook<GetAddressesHook> = SWRHook<GetAddressesHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<GetAddressesHook> = async ({ export const fetcher: HookFetcherFn<GetAddressesHook> = async ({

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseRemoveItem< export type UseRemoveItem<
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook> H extends MutationHook<RemoveItemHook> = MutationHook<RemoveItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseUpdateItem< export type UseUpdateItem<
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook> H extends MutationHook<UpdateItemHook> = MutationHook<UpdateItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseAddItem< export type UseAddItem<
H extends MutationHook<AddItemHook<any>> = MutationHook<AddItemHook> H extends MutationHook<AddItemHook> = MutationHook<AddItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher export const fetcher: HookFetcherFn<AddItemHook> = mutationFetcher

View File

@ -6,9 +6,8 @@ import Cookies from 'js-cookie'
import { useHook, useSWRHook } from '../../utils/use-hook' import { useHook, useSWRHook } from '../../utils/use-hook'
import { Provider, useCommerce } from '../..' import { Provider, useCommerce } from '../..'
export type UseCards< export type UseCards<H extends SWRHook<GetCardsHook> = SWRHook<GetCardsHook>> =
H extends SWRHook<GetCardsHook<any>> = SWRHook<GetCardsHook> ReturnType<H['useHook']>
> = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<GetCardsHook> = async ({ export const fetcher: HookFetcherFn<GetCardsHook> = async ({
options, options,

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseRemoveItem< export type UseRemoveItem<
H extends MutationHook<RemoveItemHook<any>> = MutationHook<RemoveItemHook> H extends MutationHook<RemoveItemHook> = MutationHook<RemoveItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher export const fetcher: HookFetcherFn<RemoveItemHook> = mutationFetcher

View File

@ -6,7 +6,7 @@ import { useHook, useMutationHook } from '../../utils/use-hook'
import { mutationFetcher } from '../../utils/default-fetcher' import { mutationFetcher } from '../../utils/default-fetcher'
export type UseUpdateItem< export type UseUpdateItem<
H extends MutationHook<UpdateItemHook<any>> = MutationHook<UpdateItemHook> H extends MutationHook<UpdateItemHook> = MutationHook<UpdateItemHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher export const fetcher: HookFetcherFn<UpdateItemHook> = mutationFetcher

View File

@ -5,7 +5,7 @@ import type { HookFetcherFn, SWRHook } from '../utils/types'
import type { Provider } from '..' import type { Provider } from '..'
export type UseCustomer< export type UseCustomer<
H extends SWRHook<CustomerHook<any>> = SWRHook<CustomerHook> H extends SWRHook<CustomerHook> = SWRHook<CustomerHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<CustomerHook> = SWRFetcher export const fetcher: HookFetcherFn<CustomerHook> = SWRFetcher

View File

@ -5,7 +5,7 @@ import type { SearchProductsHook } from '../types/product'
import type { Provider } from '..' import type { Provider } from '..'
export type UseSearch< export type UseSearch<
H extends SWRHook<SearchProductsHook<any>> = SWRHook<SearchProductsHook> H extends SWRHook<SearchProductsHook> = SWRHook<SearchProductsHook>
> = ReturnType<H['useHook']> > = ReturnType<H['useHook']>
export const fetcher: HookFetcherFn<SearchProductsHook> = SWRFetcher export const fetcher: HookFetcherFn<SearchProductsHook> = SWRFetcher

View File

@ -0,0 +1,18 @@
import { z } from 'zod'
export const pageSchema = z.object({
id: z.string(),
name: z.string(),
url: z.string().startsWith('/').optional(),
body: z.string(),
is_visible: z.boolean().optional(),
sort_order: z.number().optional(),
})
export const pagesPathsSchema = z.array(
z.object({
page: z.object({
path: z.string().startsWith('/'),
}),
})
)

View File

@ -0,0 +1,60 @@
import { z } from 'zod'
export const productPriceSchema = z.object({
value: z.number(),
currencyCode: z.string().max(3).optional(),
retailPrice: z.number().optional(),
})
export const productOptionSchema = z.object({
id: z.string(),
displayName: z.string(),
values: z.array(
z.object({
label: z.string(),
hexColors: z.array(z.string()).optional(),
})
),
})
export const productImageSchema = z.object({
url: z.string().url().or(z.string().startsWith('/')),
alt: z.string().optional(),
width: z.number().optional(),
height: z.number().optional(),
})
export const productVariantSchema = z.object({
id: z.string(),
sku: z.string().nullish(),
name: z.string().optional(),
options: z.array(productOptionSchema),
image: productImageSchema.optional(),
})
export const productSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string(),
descriptionHtml: z.string().optional(),
sku: z.string().nullish(),
slug: z.string(),
path: z.string().startsWith('/'),
images: z.array(productImageSchema),
variants: z.array(productVariantSchema),
price: productPriceSchema,
options: z.array(productOptionSchema),
vendor: z.string().optional(),
})
export const productsPathsSchema = z.array(
z.object({ path: z.string().startsWith('/') })
)
export const searchProductBodySchema = z.object({
search: z.string(),
categoryId: z.string(),
brandId: z.string().optional(),
sort: z.string().optional(),
locale: z.string().optional(),
})

View File

@ -0,0 +1,18 @@
import { z } from 'zod'
export const siteInfoSchema = z.object({
categories: z.array(
z.object({
id: z.string(),
name: z.string(),
path: z.string().startsWith('/'),
})
),
brands: z.array(
z.object({
id: z.string(),
name: z.string(),
path: z.string().startsWith('/'),
})
),
})

View File

@ -1,177 +1,264 @@
import type { Discount, Measurement, Image } from './common' import type { Discount, Image, Measurement } from './common'
export type SelectedOption = { // TODO: This should use the same type as the `ProductVariant` type from `product.ts`
// The option's id. export interface ProductVariant {
id?: string /**
// The product options name. * The unique identifier for the variant.
name: string */
/// The product options value.
value: string
}
export type LineItem = {
id: string id: string
variantId: string /**
productId: string * The SKU (stock keeping unit) associated with the product variant.
*/
sku?: string
/**
* The product variants name, or the product's name.
*/
name: string name: string
quantity: number /**
discounts: Discount[] * The product variants price after all discounts are applied.
// A human-friendly unique string automatically generated from the products name */
path: string
variant: ProductVariant
options?: SelectedOption[]
}
export type ProductVariant = {
id: string
// The SKU (stock keeping unit) associated with the product variant.
sku: string
// The product variants title, or the product's name.
name: string
// Whether a customer needs to provide a shipping address when placing
// an order for the product variant.
requiresShipping: boolean
// The product variants price after all discounts are applied.
price: number price: number
// Product variants price, as quoted by the manufacturer/distributor. /**
* The product variants price before discounts are applied.
*/
listPrice: number listPrice: number
// Image associated with the product variant. Falls back to the product image /**
// if no image is available. * Indicates if the variant is available for sale.
image?: Image */
// Indicates whether this product variant is in stock.
isInStock?: boolean
// Indicates if the product variant is available for sale.
availableForSale?: boolean availableForSale?: boolean
// The variant's weight. If a weight was not explicitly specified on the /**
// variant this will be the product's weight. * Whether a customer needs to provide a shipping address when placing
* an order for the product variant.
*/
requiresShipping?: boolean
/**
* The image associated with the variant.
*/
image?: Image
/**
* The variant's weight. If a weight was not explicitly specified on the
* variant, this will be the product's weight.
*/
weight?: Measurement weight?: Measurement
// The variant's height. If a height was not explicitly specified on the /**
// variant, this will be the product's height. * The variant's height. If a height was not explicitly specified on the
* variant, this will be the product's height.
*/
height?: Measurement height?: Measurement
// The variant's width. If a width was not explicitly specified on the /**
// variant, this will be the product's width. * The variant's width. If a width was not explicitly specified on the
* variant, this will be the product's width.
*/
width?: Measurement width?: Measurement
// The variant's depth. If a depth was not explicitly specified on the /**
// variant, this will be the product's depth. * The variant's depth. If a depth was not explicitly specified on the
* variant, this will be the product's depth.
*/
depth?: Measurement depth?: Measurement
} }
// Shopping cart, a.k.a Checkout export interface SelectedOption {
export type Cart = { /**
* The unique identifier for the option.
*/
id?: string
/**
* The product options name, such as "Color" or "Size".
*/
name: string
/**
* The product options value, such as "Red" or "XL".
*/
value: string
}
export interface LineItem {
/**
* The unique identifier for the line item.
*/
id: string id: string
// ID of the customer to which the cart belongs. /**
* The unique identifier for the product variant.
*/
variantId: string
/**
* The unique identifier for the product, if the variant is not provided.
*/
productId: string
/**
* This is usually the product's name.
*/
name: string
/**
* The quantity of the product variant in the line item.
*/
quantity: number
/**
* List of discounts applied to the line item.
*/
discounts: Discount[]
/**
* A human-friendly unique string automatically generated from the products name.
*/
path: string
/**
* The product variant.
*/
variant: ProductVariant
/**
* List of selected options, to be used when displaying the line item, such as Color: Red, Size: XL.
*/
options?: SelectedOption[]
}
/**
* Shopping cart, a.k.a Checkout
*/
export interface Cart {
/**
* The unique identifier for the cart.
*/
id: string
/**
* ID of the customer to which the cart belongs.
*/
customerId?: string customerId?: string
// The email assigned to this cart /**
* The URL of the cart.
*/
url?: string
/**
* The email assigned to this cart.
*/
email?: string email?: string
// The date and time when the cart was created. /**
* The date and time when the cart was created.
*/
createdAt: string createdAt: string
// The currency used for this cart /**
* The currency used for this cart */
currency: { code: string } currency: { code: string }
// Specifies if taxes are included in the line items. /**
* Indicates if taxes are included in the line items.
*/
taxesIncluded: boolean taxesIncluded: boolean
/**
* List of cart line items.
*/
lineItems: LineItem[] lineItems: LineItem[]
// The sum of all the prices of all the items in the cart. /**
// Duties, taxes, shipping and discounts excluded. * The sum of all the pricexs of all the items in the cart.
* Duties, taxes, shipping and discounts excluded.
*/
lineItemsSubtotalPrice: number lineItemsSubtotalPrice: number
// Price of the cart before duties, shipping and taxes. /**
* Price of the cart before duties, shipping and taxes.*/
subtotalPrice: number subtotalPrice: number
// The sum of all the prices of all the items in the cart. /**
// Duties, taxes and discounts included. * The sum of all the prices of all the items in the cart.
* Duties, taxes and discounts included.
*/
totalPrice: number totalPrice: number
// Discounts that have been applied on the cart. /**
* Discounts that have been applied on the cart.
*/
discounts?: Discount[] discounts?: Discount[]
} }
/** /**
* Base cart item body used for cart mutations * Base cart item body used for cart mutations
*/ */
export type CartItemBody = { export interface CartItemBody {
/**
* The unique identifier for the product variant.
*/
variantId: string variantId: string
/**
* The unique identifier for the product, if the variant is not provided.
*/
productId?: string productId?: string
/**
* The quantity of the product variant.
*/
quantity?: number quantity?: number
/**
* The product variant's selected options.
*/
optionsSelected?: SelectedOption[]
} }
/** /**
* Hooks schema * Cart Hooks for add, update and remove items from the cart
*/ */
export type CartHooks = {
export type CartTypes = { getCart: GetCartHook
cart?: Cart addItem: AddItemHook
item: LineItem updateItem: UpdateItemHook
itemBody: CartItemBody removeItem: RemoveItemHook
} }
export type CartHooks<T extends CartTypes = CartTypes> = { export type GetCartHook = {
getCart: GetCartHook<T> data: Cart | null
addItem: AddItemHook<T>
updateItem: UpdateItemHook<T>
removeItem: RemoveItemHook<T>
}
export type GetCartHook<T extends CartTypes = CartTypes> = {
data: T['cart'] | null
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
} }
export type AddItemHook<T extends CartTypes = CartTypes> = { export type AddItemHook = {
data: T['cart'] data: Cart
input?: T['itemBody'] input?: CartItemBody
fetcherInput: T['itemBody'] fetcherInput: CartItemBody
body: { item: T['itemBody'] } body: { item: CartItemBody }
actionInput: T['itemBody'] actionInput: CartItemBody
} }
export type UpdateItemHook<T extends CartTypes = CartTypes> = { export type UpdateItemHook = {
data: T['cart'] | null data: Cart | null | undefined
input: { item?: T['item']; wait?: number } input: { item?: LineItem; wait?: number }
fetcherInput: { itemId: string; item: T['itemBody'] } fetcherInput: { itemId: string; item: CartItemBody }
body: { itemId: string; item: T['itemBody'] } body: { itemId: string; item: CartItemBody }
actionInput: T['itemBody'] & { id: string } actionInput: CartItemBody & { id: string }
} }
export type RemoveItemHook<T extends CartTypes = CartTypes> = { export type RemoveItemHook = {
data: T['cart'] | null data: Cart | null | undefined
input: { item?: T['item'] } input: { item?: LineItem }
fetcherInput: { itemId: string } fetcherInput: { itemId: string }
body: { itemId: string } body: { itemId: string }
actionInput: { id: string } actionInput: { id: string }
} }
/** /**
* API Schema * Cart API endpoitns & handlers for add, update and remove items from the cart
*/ */
export type CartSchema = {
export type CartSchema<T extends CartTypes = CartTypes> = {
endpoint: { endpoint: {
options: {} options: {}
handlers: CartHandlers<T> handlers: CartHandlers
} }
} }
export type CartHandlers<T extends CartTypes = CartTypes> = { export type CartHandlers = {
getCart: GetCartHandler<T> getCart: GetCartHandler
addItem: AddItemHandler<T> addItem: AddItemHandler
updateItem: UpdateItemHandler<T> updateItem: UpdateItemHandler
removeItem: RemoveItemHandler<T> removeItem: RemoveItemHandler
} }
export type GetCartHandler<T extends CartTypes = CartTypes> = GetCartHook<T> & { export type GetCartHandler = GetCartHook & {
body: { cartId?: string } body: { cartId?: string }
} }
export type AddItemHandler<T extends CartTypes = CartTypes> = AddItemHook<T> & { export type AddItemHandler = AddItemHook & {
body: { cartId: string } body: { cartId: string }
} }
export type UpdateItemHandler<T extends CartTypes = CartTypes> = export type UpdateItemHandler = UpdateItemHook & {
UpdateItemHook<T> & { data: Cart
data: T['cart'] body: { cartId: string }
body: { cartId: string } }
}
export type RemoveItemHandler<T extends CartTypes = CartTypes> = export type RemoveItemHandler = RemoveItemHook & {
RemoveItemHook<T> & { body: { cartId: string }
body: { cartId: string } }
}

View File

@ -1,57 +1,89 @@
import type { UseSubmitCheckout } from '../checkout/use-submit-checkout' import type { UseSubmitCheckout } from '../checkout/use-submit-checkout'
import type { Address, AddressFields } from './customer/address' import type { AddressFields } from './customer/address'
import type { Card, CardFields } from './customer/card' import type { Card, CardFields } from './customer/card'
import type { LineItem } from './cart'
// Index export interface Checkout {
export type Checkout = any /**
* Indicates if the checkout has payment iformation collected.
export type CheckoutTypes = { */
card?: Card | CardFields hasPayment: boolean
address?: Address | AddressFields /**
checkout?: Checkout * Indicates if the checkout has shipping information collected.
hasPayment?: boolean */
hasShipping?: boolean hasShipping: boolean
/**
* The unique identifier for the address that the customer has selected for shipping.
*/
addressId: string
/**
* The list of payment cards that the customer has available.
*/
payments?: Card[]
/**
* The unique identifier of the card that the customer has selected for payment.
*/
cardId?: string
/**
* List of items in the checkout.
*/
lineItems?: LineItem[]
} }
export type SubmitCheckoutHook<T extends CheckoutTypes = CheckoutTypes> = { export interface CheckoutBody {
data: T /**
input?: T * The unique identifier for the cart.
fetcherInput: T */
body: { item: T } cartId?: string
actionInput: T /**
* The Card information.
* @see CardFields
*/
card: CardFields
/**
* The Address information.
* @see AddressFields
*/
address: AddressFields
} }
export type GetCheckoutHook<T extends CheckoutTypes = CheckoutTypes> = { export type SubmitCheckoutHook = {
data: T['checkout'] | null data: Checkout
input?: CheckoutBody
fetcherInput: CheckoutBody
body: { item: CheckoutBody }
actionInput: CheckoutBody
}
export type GetCheckoutHook = {
data: Checkout | null
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
mutations: { submit: UseSubmitCheckout } mutations: { submit: UseSubmitCheckout }
} }
export type CheckoutHooks<T extends CheckoutTypes = CheckoutTypes> = { export type CheckoutHooks = {
submitCheckout?: SubmitCheckoutHook<T> submitCheckout?: SubmitCheckoutHook
getCheckout: GetCheckoutHook<T> getCheckout: GetCheckoutHook
} }
export type GetCheckoutHandler<T extends CheckoutTypes = CheckoutTypes> = export type GetCheckoutHandler = GetCheckoutHook & {
GetCheckoutHook<T> & { body: { cartId: string }
body: { cartId: string }
}
export type SubmitCheckoutHandler<T extends CheckoutTypes = CheckoutTypes> =
SubmitCheckoutHook<T> & {
body: { cartId: string }
}
export type CheckoutHandlers<T extends CheckoutTypes = CheckoutTypes> = {
getCheckout: GetCheckoutHandler<T>
submitCheckout?: SubmitCheckoutHandler<T>
} }
export type CheckoutSchema<T extends CheckoutTypes = CheckoutTypes> = { export type SubmitCheckoutHandler = SubmitCheckoutHook & {
body: { cartId: string }
}
export type CheckoutHandlers = {
getCheckout: GetCheckoutHandler
submitCheckout?: SubmitCheckoutHandler
}
export type CheckoutSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: CheckoutHandlers<T> handlers: CheckoutHandlers
} }
} }

View File

@ -1,16 +1,36 @@
export type Discount = { export interface Discount {
// The value of the discount, can be an amount or percentage /**
* The value of the discount, can be an amount or percentage.
*/
value: number value: number
} }
export type Measurement = { export interface Measurement {
/**
* The measurement's value.
*/
value: number value: number
/**
* The measurement's unit, such as "KILOGRAMS", "GRAMS", "POUNDS" & "OOUNCES".
*/
unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES' unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES'
} }
export type Image = { export interface Image {
/**
* The URL of the image.
*/
url: string url: string
altText?: string /**
* A word or phrase that describes the content of an image.
*/
alt?: string
/**
* The image's width.
*/
width?: number width?: number
/**
* The image's height.
*/
height?: number height?: number
} }

View File

@ -1,111 +1,123 @@
export interface Address { export interface Address {
/**
* The unique identifier for the address.
*/
id: string id: string
/**
* The customer's first name.
*/
mask: string mask: string
} }
export interface AddressFields { export interface AddressFields {
/**
* The type of address.
* @example "billing, shipping"
*/
type: string type: string
/**
* The customer's first name.
*/
firstName: string firstName: string
/**
* The customer's last name.
*/
lastName: string lastName: string
/**
* Company name.
*/
company: string company: string
/**
* The customer's billing address street number.
*/
streetNumber: string streetNumber: string
/**
* The customer's billing address apartment number.
*/
apartments: string apartments: string
/**
* The customer's billing address zip code.
*/
zipCode: string zipCode: string
/**
* The customer's billing address city.
*/
city: string city: string
/**
* The customer's billing address country.
*/
country: string country: string
} }
export type CustomerAddressTypes = { /**
address?: Address * Hooks for managing a customer's addresses.
fields: AddressFields */
}
export type GetAddressesHook< export type GetAddressesHook = {
T extends CustomerAddressTypes = CustomerAddressTypes data: Address[] | null
> = {
data: T['address'][] | null
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
} }
export type AddItemHook<T extends CustomerAddressTypes = CustomerAddressTypes> = export type AddItemHook = {
{ data: Address
data: T['address'] input?: AddressFields
input?: T['fields'] fetcherInput: AddressFields
fetcherInput: T['fields'] body: { item: AddressFields }
body: { item: T['fields'] } actionInput: AddressFields
actionInput: T['fields']
}
export type UpdateItemHook<
T extends CustomerAddressTypes = CustomerAddressTypes
> = {
data: T['address'] | null
input: { item?: T['fields']; wait?: number }
fetcherInput: { itemId: string; item: T['fields'] }
body: { itemId: string; item: T['fields'] }
actionInput: T['fields'] & { id: string }
} }
export type RemoveItemHook< export type UpdateItemHook = {
T extends CustomerAddressTypes = CustomerAddressTypes data: Address | null
> = { input: { item?: AddressFields; wait?: number }
data: T['address'] | null fetcherInput: { itemId: string; item: AddressFields }
input: { item?: T['address'] } body: { itemId: string; item: AddressFields }
actionInput: AddressFields & { id: string }
}
export type RemoveItemHook = {
data: Address | null | undefined
input: { item?: Address }
fetcherInput: { itemId: string } fetcherInput: { itemId: string }
body: { itemId: string } body: { itemId: string }
actionInput: { id: string } actionInput: { id: string }
} }
export type CustomerAddressHooks< export type CustomerAddressHooks = {
T extends CustomerAddressTypes = CustomerAddressTypes getAddresses: GetAddressesHook
> = { addItem: AddItemHook
getAddresses: GetAddressesHook<T> updateItem: UpdateItemHook
addItem: AddItemHook<T> removeItem: RemoveItemHook
updateItem: UpdateItemHook<T>
removeItem: RemoveItemHook<T>
} }
export type AddressHandler< /**
T extends CustomerAddressTypes = CustomerAddressTypes * API endpoints for managing a customer's addresses.
> = GetAddressesHook<T> & { */
body: { cartId?: string }
}
export type AddItemHandler< export type AddItemHandler = AddItemHook & {
T extends CustomerAddressTypes = CustomerAddressTypes
> = AddItemHook<T> & {
body: { cartId: string } body: { cartId: string }
} }
export type UpdateItemHandler< export type UpdateItemHandler = UpdateItemHook & {
T extends CustomerAddressTypes = CustomerAddressTypes data: Address
> = UpdateItemHook<T> & {
data: T['address']
body: { cartId: string } body: { cartId: string }
} }
export type RemoveItemHandler< export type RemoveItemHandler = RemoveItemHook & {
T extends CustomerAddressTypes = CustomerAddressTypes
> = RemoveItemHook<T> & {
body: { cartId: string } body: { cartId: string }
} }
export type CustomerAddressHandlers< export type CustomerAddressHandlers = {
T extends CustomerAddressTypes = CustomerAddressTypes getAddresses: GetAddressesHook
> = { addItem: AddItemHandler
getAddresses: GetAddressesHook<T> updateItem: UpdateItemHandler
addItem: AddItemHandler<T> removeItem: RemoveItemHandler
updateItem: UpdateItemHandler<T>
removeItem: RemoveItemHandler<T>
} }
export type CustomerAddressSchema< export type CustomerAddressSchema = {
T extends CustomerAddressTypes = CustomerAddressTypes
> = {
endpoint: { endpoint: {
options: {} options: {}
handlers: CustomerAddressHandlers<T> handlers: CustomerAddressHandlers
} }
} }

View File

@ -1,102 +1,139 @@
export interface Card { export interface Card {
/**
* Unique identifier for the card.
*/
id: string id: string
/**
* Masked card number. Contains only the last 4 digits.
* @example "4242"
*/
mask: string mask: string
/**
* The card's brand.
* @example "Visa, Mastercard, etc."
*/
provider: string provider: string
} }
/**
* The fields required to create a new card.
*/
export interface CardFields { export interface CardFields {
/**
* Name on the card.
*/
cardHolder: string cardHolder: string
/**
* The card's number, consisting of 16 digits.
*/
cardNumber: string cardNumber: string
/**
* The card's expiry month and year, in the format MM/YY.
* @example "01/25"
*/
cardExpireDate: string cardExpireDate: string
/**
* The card's security code, consisting of 3 digits.
*/
cardCvc: string cardCvc: string
/**
* The customer's first name.
*/
firstName: string firstName: string
/**
* The customer's last name.
*/
lastName: string lastName: string
/**
* Company name.
*/
company: string company: string
/**
* The customer's billing address street number.
*/
streetNumber: string streetNumber: string
/**
* The customer's billing address zip code.
*/
zipCode: string zipCode: string
/**
* The customer's billing address city.
*/
city: string city: string
/**
* The customer's billing address country.
*/
country: string country: string
} }
export type CustomerCardTypes = { /**
card?: Card * Hooks for managing a customer's cards.
fields: CardFields */
}
export type GetCardsHook<T extends CustomerCardTypes = CustomerCardTypes> = { export type GetCardsHook = {
data: T['card'][] | null data: Card[] | null
input: {} input: {}
fetcherInput: { cartId?: string } fetcherInput: { cartId?: string }
swrState: { isEmpty: boolean } swrState: { isEmpty: boolean }
} }
export type AddItemHook<T extends CustomerCardTypes = CustomerCardTypes> = { export type AddItemHook = {
data: T['card'] data: Card
input?: T['fields'] input?: CardFields
fetcherInput: T['fields'] fetcherInput: CardFields
body: { item: T['fields'] } body: { item: CardFields }
actionInput: T['fields'] actionInput: CardFields
} }
export type UpdateItemHook<T extends CustomerCardTypes = CustomerCardTypes> = { export type UpdateItemHook = {
data: T['card'] | null data: Card | null | undefined
input: { item?: T['fields']; wait?: number } input: { item?: CardFields; wait?: number }
fetcherInput: { itemId: string; item: T['fields'] } fetcherInput: { itemId: string; item: CardFields }
body: { itemId: string; item: T['fields'] } body: { itemId: string; item: CardFields }
actionInput: T['fields'] & { id: string } actionInput: CardFields & { id: string }
} }
export type RemoveItemHook<T extends CustomerCardTypes = CustomerCardTypes> = { export type RemoveItemHook = {
data: T['card'] | null data: Card | null | undefined
input: { item?: T['card'] } input: { item?: Card }
fetcherInput: { itemId: string } fetcherInput: { itemId: string }
body: { itemId: string } body: { itemId: string }
actionInput: { id: string } actionInput: { id: string }
} }
export type CustomerCardHooks<T extends CustomerCardTypes = CustomerCardTypes> = export interface CustomerCardHooks {
{ getCards: GetCardsHook
getCards: GetCardsHook<T> addItem: AddItemHook
addItem: AddItemHook<T> updateItem: UpdateItemHook
updateItem: UpdateItemHook<T> removeItem: RemoveItemHook
removeItem: RemoveItemHook<T>
}
export type CardsHandler<T extends CustomerCardTypes = CustomerCardTypes> =
GetCardsHook<T> & {
body: { cartId?: string }
}
export type AddItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
AddItemHook<T> & {
body: { cartId: string }
}
export type UpdateItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
UpdateItemHook<T> & {
data: T['card']
body: { cartId: string }
}
export type RemoveItemHandler<T extends CustomerCardTypes = CustomerCardTypes> =
RemoveItemHook<T> & {
body: { cartId: string }
}
export type CustomerCardHandlers<
T extends CustomerCardTypes = CustomerCardTypes
> = {
getCards: GetCardsHook<T>
addItem: AddItemHandler<T>
updateItem: UpdateItemHandler<T>
removeItem: RemoveItemHandler<T>
} }
export type CustomerCardSchema< /**
T extends CustomerCardTypes = CustomerCardTypes * Customer card API handlers.
> = { */
export type AddItemHandler = AddItemHook & {
body: { cartId: string }
}
export type UpdateItemHandler = UpdateItemHook & {
data: Card
body: { cartId: string }
}
export type RemoveItemHandler = RemoveItemHook & {
body: { cartId: string }
}
export type CustomerCardHandlers = {
getCards: GetCardsHook
addItem: AddItemHandler
updateItem: UpdateItemHandler
removeItem: RemoveItemHandler
}
export type CustomerCardSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: CustomerCardHandlers<T> handlers: CustomerCardHandlers
} }
} }

View File

@ -1,24 +1,53 @@
export * as Card from './card' export * as Card from './card'
export * as Address from './address' export * as Address from './address'
// TODO: define this type export interface Customer {
export type Customer = any /**
* The unique identifier for the customer.
export type CustomerTypes = { */
customer: Customer id: string
/**
* The customer's first name.
*/
firstName: string
/**
* The customer's last name.
*/
lastName: string
/**
* The customer's email address.
*/
email?: string
/**
* The customer's phone number.
* @optional
*/
phone?: string
/**
* The customer's company name.
*/
company?: string
/**
* The customer's notes.
*/
notes?: string
/**
* Indicates wathever the customer accepts marketing, such as email newsletters.
*/
acceptsMarketing?: boolean
} }
export type CustomerHook<T extends CustomerTypes = CustomerTypes> = { export type CustomerHook = {
data: T['customer'] | null data: Customer | null | undefined
fetchData: { customer: T['customer'] } | null fetchData: { customer: Customer } | null
} }
export type CustomerSchema<T extends CustomerTypes = CustomerTypes> = { export type CustomerSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: { handlers: {
getLoggedInCustomer: { getLoggedInCustomer: {
data: { customer: T['customer'] } | null data: { customer: Customer } | null
} }
} }
} }

View File

@ -1,24 +1,26 @@
export type LoginBody = { export interface LoginBody {
/**
* The user's email address.
*/
email: string email: string
/**
* The user's password.
*/
password: string password: string
} }
export type LoginTypes = { export type LoginHook = {
body: LoginBody
}
export type LoginHook<T extends LoginTypes = LoginTypes> = {
data: null data: null
actionInput: LoginBody actionInput: LoginBody
fetcherInput: LoginBody fetcherInput: LoginBody
body: T['body'] body: LoginBody
} }
export type LoginSchema<T extends LoginTypes = LoginTypes> = { export type LoginSchema = {
endpoint: { endpoint: {
options: {} options: {}
handlers: { handlers: {
login: LoginHook<T> login: LoginHook
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More