Merge branch 'main' into update-image

This commit is contained in:
Catalin Pinte 2022-11-28 16:57:18 +02:00
commit 2805323513
11 changed files with 47 additions and 31 deletions

View File

@ -20,11 +20,11 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)
## Run minimal version locally ## 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 > To run a minimal version of Next.js Commerce you can start with the default local provider `@vercel/commerce-local` that has all features disabled (cart, auth) and uses static files for the backend
```bash ```bash
pnpm install & pnpm build # run this commands in root folder of the mono repo pnpm install & pnpm build # run these commands in the root folder of the mono repo
pnpm dev # run this commands in the site folder pnpm dev # run this command in the site folder
``` ```
> If you encounter any problems while installing and running for the first time, please see the Troubleshoot section > If you encounter any problems while installing and running for the first time, please see the Troubleshoot section
@ -47,10 +47,10 @@ Next.js Commerce integrates out-of-the-box with BigCommerce, Shopify, Swell, Sal
## Considerations ## Considerations
- `packages/commerce` contains all types, helpers and functions to be used as base to build a new **provider**. - `packages/commerce` contains all types, helpers and functions to be used as a base to build a new **provider**.
- **Providers** live under `packages`'s root folder and they will extend Next.js Commerce types and functionality (`packages/commerce`). - **Providers** live under `packages`'s root folder and they will extend Next.js Commerce types and functionality (`packages/commerce`).
- We have a **Features API** to ensure feature parity between the UI and the Provider. The UI should update accordingly and no extra code should be bundled. All extra configuration for features will live under `features` in `commerce.config.json` and if needed it can also be accessed programatically. - We have a **Features API** to ensure feature parity between the UI and the Provider. The UI should update accordingly and no extra code should be bundled. All extra configuration for features will live under `features` in `commerce.config.json` and if needed it can also be accessed programmatically.
- Each **provider** should add its corresponding `next.config.js` and `commerce.config.json` adding specific data related to the provider. For example in case of BigCommerce, the images CDN and additional API routes. - Each **provider** should add its corresponding `next.config.js` and `commerce.config.json` adding specific data related to the provider. For example in the case of BigCommerce, the images CDN and additional API routes.
## Configuration ## Configuration
@ -73,7 +73,7 @@ Every provider defines the features that it supports under `packages/{provider}/
#### Features Available #### Features Available
The following features can be enabled or disabled. This means that the UI will remove all code related to the feature. The following features can be enabled or disabled. This means that the UI will remove all code related to the feature.
For example: Turning `cart` off will disable Cart capabilities. For example: turning `cart` off will disable Cart capabilities.
- cart - cart
- search - search
@ -83,7 +83,7 @@ For example: Turning `cart` off will disable Cart capabilities.
#### How to turn Features on and off #### How to turn Features on and off
> NOTE: The selected provider should support the feature that you are toggling. (This means that you can't turn wishlist on if the provider doesn't support this functionality out the box) > NOTE: The selected provider should support the feature that you are toggling. (This means that you can't turn wishlist on if the provider doesn't support this functionality out of the box)
- Open `site/commerce.config.json` - Open `site/commerce.config.json`
- You'll see a config file like this: - You'll see a config file like this:

View File

@ -42,7 +42,9 @@ export class CommerceNetworkError extends Error {
} }
export const normalizeZodIssues = (issues: ZodError['issues']) => export const normalizeZodIssues = (issues: ZodError['issues']) =>
issues.map(({ path, message }) => `${message} at "${path.join('.')}" field`) issues.map(({ path, message }) =>
path.length ? `${message} at "${path.join('.')}" field` : message
)
export const getOperationError = (operation: string, error: unknown) => { export const getOperationError = (operation: string, error: unknown) => {
if (error instanceof ZodError) { if (error instanceof ZodError) {

View File

@ -21,7 +21,7 @@ export const withOperationCallback =
const parse = ({ name, data }: Operation) => { const parse = ({ name, data }: Operation) => {
switch (name) { switch (name) {
case 'getProduct': case 'getProduct':
productSchema.nullable().parse(data.product) productSchema.optional().parse(data.product)
break break
case 'getAllProducts': case 'getAllProducts':
z.array(productSchema).parse(data.products) z.array(productSchema).parse(data.products)

View File

@ -27,7 +27,7 @@ export default function getSiteInfoOperation({
const { sdkFetch } = commerce.getConfig(config) const { sdkFetch } = commerce.getConfig(config)
const { data: categories } = await sdkFetch('categories', 'list') const { data: categories } = await sdkFetch('categories', 'list')
const formattedCategories = categories.map(normalizeCategory) const formattedCategories = categories?.map(normalizeCategory) ?? []
return { return {
categories: formattedCategories, categories: formattedCategories,

View File

@ -22,17 +22,17 @@ export const handler: MutationHook<AddItemHook> = {
variables.push(item.variantId) variables.push(item.variantId)
} }
const { cart } = await fetch<{ cart: CommercejsCart }>({ const cart = await fetch<CommercejsCart>({
query: options.query, query: options.query,
method: options.method, method: options.method,
variables, variables,
}) })
return normalizeCart(cart) return normalizeCart(cart)
}, },
useHook: ({ fetch }) => useHook: ({ fetch }) =>
function useHook() { function useHook() {
const { mutate } = useCart() const { mutate } = useCart()
return useCallback( return useCallback(
async function addItem(input) { async function addItem(input) {
const cart = await fetch({ input }) const cart = await fetch({ input })

View File

@ -16,7 +16,7 @@ export const handler: MutationHook<RemoveItemHook> = {
method: 'remove', method: 'remove',
}, },
async fetcher({ input, options, fetch }) { async fetcher({ input, options, fetch }) {
const { cart } = await fetch<{ cart: CommercejsCart }>({ const cart = await fetch<CommercejsCart>({
query: options.query, query: options.query,
method: options.method, method: options.method,
variables: input.itemId, variables: input.itemId,

View File

@ -30,7 +30,7 @@ export const handler = {
}, },
async fetcher({ input, options, fetch }: HookFetcherContext<UpdateItemHook>) { async fetcher({ input, options, fetch }: HookFetcherContext<UpdateItemHook>) {
const variables = [input.itemId, { quantity: input.item.quantity }] const variables = [input.itemId, { quantity: input.item.quantity }]
const { cart } = await fetch<{ cart: CommercejsCart }>({ const cart = await fetch<CommercejsCart>({
query: options.query, query: options.query,
method: options.method, method: options.method,
variables, variables,
@ -57,7 +57,7 @@ export const handler = {
const variantId = input.productId ?? item?.variantId const variantId = input.productId ?? item?.variantId
const quantity = input?.quantity ?? item?.quantity const quantity = input?.quantity ?? item?.quantity
if (!itemId || !productId || !variantId) { if (!itemId || !productId) {
throw new ValidationError({ throw new ValidationError({
message: 'Invalid input for updating cart item', message: 'Invalid input for updating cart item',
}) })
@ -69,7 +69,7 @@ export const handler = {
item: { item: {
quantity, quantity,
productId, productId,
variantId, variantId: variantId ?? '',
}, },
}, },
}) })

View File

@ -44,14 +44,16 @@ const normalizeLineItem = (
} }
} }
export const normalizeCart = (commercejsCart: CommercejsCart): Cart => { export const normalizeCart = (
commercejsCart: CommercejsCart | { cart: CommercejsCart }
): Cart => {
const { const {
id, id,
created, created,
subtotal: { raw: rawPrice }, subtotal: { raw: rawPrice },
currency, currency,
line_items, line_items,
} = commercejsCart } = 'cart' in commercejsCart ? commercejsCart.cart : commercejsCart
return { return {
id, id,

View File

@ -54,6 +54,7 @@ export function normalizeProduct(
): Product { ): Product {
const { id, name, description, permalink, assets, price, variant_groups } = const { id, name, description, permalink, assets, price, variant_groups } =
commercejsProduct commercejsProduct
return { return {
id, id,
name, name,
@ -61,15 +62,19 @@ export function normalizeProduct(
descriptionHtml: description, descriptionHtml: description,
slug: permalink, slug: permalink,
path: `/${permalink}`, path: `/${permalink}`,
images: assets.map(({ url, description, filename }) => ({ images:
url, assets?.map(({ url, description, filename }) => ({
alt: description || filename, url,
})), alt: description || filename,
})) || [],
price: { price: {
value: price.raw, value: price.raw,
currencyCode: 'USD', currencyCode: 'USD',
}, },
variants: normalizeVariants(commercejsProductVariants, variant_groups), variants: normalizeVariants(
options: getOptionsFromVariantGroups(variant_groups), commercejsProductVariants,
variant_groups || []
),
options: variant_groups ? getOptionsFromVariantGroups(variant_groups) : [],
} }
} }

View File

@ -2,7 +2,6 @@ import type { GraphQLFetcher } from '@vercel/commerce/api'
import { API_URL } from '../../const' import { API_URL } from '../../const'
import { getError } from '../../utils/handle-fetch-response' import { getError } from '../../utils/handle-fetch-response'
import { getCommerceApi } from '..'
import { getToken } from '../../utils/index' import { getToken } from '../../utils/index'
const fetchGraphqlApi: GraphQLFetcher = async ( const fetchGraphqlApi: GraphQLFetcher = async (
@ -10,7 +9,6 @@ const fetchGraphqlApi: GraphQLFetcher = async (
{ variables } = {}, { variables } = {},
headers?: HeadersInit headers?: HeadersInit
) => { ) => {
const config = getCommerceApi().getConfig()
const token = getToken() const token = getToken()
const res = await fetch(API_URL!, { const res = await fetch(API_URL!, {
@ -28,10 +26,17 @@ const fetchGraphqlApi: GraphQLFetcher = async (
}), }),
}) })
const { data, errors, status } = await res.json() const { data, errors, message, type, status } = await res.json()
if (errors) { if (errors || res.status >= 400) {
throw getError(errors, status) throw getError(
errors || [
{
message: `${type ? `${type}, ` : ''}${message}`,
},
],
status || res.status
)
} }
return { data, res } return { data, res }

View File

@ -35,7 +35,9 @@ export async function getStaticProps({
const { products: relatedProducts } = await allProductsPromise const { products: relatedProducts } = await allProductsPromise
if (!product) { if (!product) {
throw new Error(`Product with slug '${params!.slug}' not found`) return {
notFound: true,
}
} }
return { return {