Merge branch 'main' of github.com:vercel/commerce into outgrow-reaction-commerce-provider

Signed-off-by: Loan Laux <llaux@trellis.co>
This commit is contained in:
Loan Laux 2022-03-09 21:41:08 +04:00
commit eced304c25
No known key found for this signature in database
GPG Key ID: 8BC393FB65B34B77
1445 changed files with 41264 additions and 5254 deletions

14
.gitignore vendored
View File

@ -2,18 +2,19 @@
# dependencies
node_modules
/.pnp
.pnp
.pnp.js
# testing
/coverage
coverage
# next.js
.next/
out/
.next
out
# production
/build
build
dist
# misc
.DS_Store
@ -34,3 +35,6 @@ yarn-error.log*
# vercel
.vercel
# Turborepo
.turbo

View File

@ -1,3 +1,5 @@
# Every package defines its prettier config
node_modules
dist
.next
public

View File

@ -2,13 +2,5 @@
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"overrides": [
{
"files": ["framework/saleor/**/*"],
"options": {
"printWidth": 120
}
}
]
"useTabs": false
}

View File

@ -1,3 +1,8 @@
{
"recommendations": ["esbenp.prettier-vscode"]
"recommendations": [
"esbenp.prettier-vscode",
"csstools.postcss",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-typescript-next"
]
}

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
@ -12,6 +12,10 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)
- BigCommerce Demo: https://bigcommerce.vercel.store/
- Vendure Demo: https://vendure.vercel.store
- Saleor Demo: https://saleor.vercel.store/
- Ordercloud Demo: https://ordercloud.vercel.store/
- Spree Demo: https://spree.vercel.store/
- Kibo Commerce Demo: https://kibocommerce.vercel.store/
- Commerce.js Demo: https://commercejs.vercel.store/
## Features
@ -27,42 +31,32 @@ Demo live at: [demo.vercel.store](https://demo.vercel.store/)
## Integrations
Next.js Commerce integrates out-of-the-box with BigCommerce, Shopify, Swell, Saleor and Vendure. We plan to support all major ecommerce backends.
Next.js Commerce integrates out-of-the-box with BigCommerce, Shopify, Swell, Saleor, Vendure, Spree and Commerce.js. We plan to support all major ecommerce backends.
## Considerations
- `framework/commerce` contains all types, helpers and functions to be used as base to build a new **provider**.
- **Providers** live under `framework`'s root folder and they will extend Next.js Commerce types and functionality (`framework/commerce`).
- `packages/commerce` contains all types, helpers and functions to be used as 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`).
- 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.
- 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.
- **Providers don't depend on anything that's specific to the application they're used in**. They only depend on `framework/commerce`, on their own framework folder and on some dependencies included in `package.json`
## Configuration
### How to change providers
Open `.env.local` and change the value of `COMMERCE_PROVIDER` to the provider you would like to use, then set the environment variables for that provider (use `.env.template` as the base).
Open `site/.env.local` and change the value of `COMMERCE_PROVIDER` to the provider you would like to use, then set the environment variables for that provider (use `site/.env.template` as the base).
The setup for Shopify would look like this for example:
```
COMMERCE_PROVIDER=shopify
COMMERCE_PROVIDER=@vercel/commerce-shopify
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=xxxxxxx.myshopify.com
```
And check that the `tsconfig.json` resolves to the chosen provider:
```
"@framework": ["framework/shopify"],
"@framework/*": ["framework/shopify/*"]
```
That's it!
### Features
Every provider defines the features that it supports under `framework/{provider}/commerce.config.json`
Every provider defines the features that it supports under `packages/{provider}/src/commerce.config.json`
#### Features Available
@ -79,7 +73,7 @@ For example: Turning `cart` off will disable Cart capabilities.
> 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)
- Open `commerce.config.json`
- Open `site/commerce.config.json`
- You'll see a config file like this:
```json
{
@ -94,7 +88,7 @@ For example: Turning `cart` off will disable Cart capabilities.
### How to create a new provider
Follow our docs for [Adding a new Commerce Provider](framework/commerce/new-provider.md).
Follow our docs for [Adding a new Commerce Provider](packages/commerce/new-provider.md).
If you succeeded building a provider, submit a PR with a valid demo and we'll review it asap.
@ -104,17 +98,17 @@ 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.
2. Create a new branch `git checkout -b MY_BRANCH_NAME`
3. Install yarn: `npm install -g yarn`
4. Install the dependencies: `yarn`
5. Duplicate `.env.template` and rename it to `.env.local`
6. Add proper store values to `.env.local`
7. Run `yarn dev` to build and watch for code changes
3. Install the dependencies: `yarn`
4. Duplicate `site/.env.template` and rename it to `site/.env.local`
5. Add proper store values to `site/.env.local`
6. Run `cd site` and `yarn dev` to build and watch for code changes
7. Run `yarn turbo run build` to check the build after your changes
## Work in progress
We're using Github Projects to keep track of issues in progress and todo's. Here is our [Board](https://github.com/vercel/commerce/projects/1)
People actively working on this project: @okbel & @lfades.
People actively working on this project: @okbel, @lfades, @dominiksipowicz, @gbibeaul.
## Troubleshoot
@ -141,7 +135,7 @@ If your project was started with a "Deploy with Vercel" button, you can use Verc
2. Link local instance with Vercel and Github accounts (creates .vercel file): `vercel link`
3. Download your environment variables: `vercel env pull .env.local`
Next, you're free to customize the starter. More updates coming soon. Stay tuned.
Next, you're free to customize the starter. More updates coming soon. Stay tuned..
</details>
@ -153,3 +147,40 @@ After Email confirmation, Checkout should be manually enabled through BigCommerc
<br>
BigCommerce team has been notified and they plan to add more details about this subject.
</details>
<details>
<summary>When run locally I get `Error: Cannot find module '...@vercel/commerce/dist/config'`</summary>
```bash
commerce/site
yarn dev
yarn run v1.22.17
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Loaded env from /commerce/site/.env.local
error - Failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error
Error: Cannot find module '/Users/dom/work/vercel/commerce/node_modules/@vercel/commerce/dist/config.cjs'
at createEsmNotFoundErr (node:internal/modules/cjs/loader:960:15)
at finalizeEsmResolution (node:internal/modules/cjs/loader:953:15)
at resolveExports (node:internal/modules/cjs/loader:482:14)
at Function.Module._findPath (node:internal/modules/cjs/loader:522:31)
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:919:27)
at Function.mod._resolveFilename (/Users/dom/work/vercel/commerce/node_modules/next/dist/build/webpack/require-hook.js:179:28)
at Function.Module._load (node:internal/modules/cjs/loader:778:27)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/Users/dom/work/vercel/commerce/site/commerce-config.js:9:14) {
code: 'MODULE_NOT_FOUND',
path: '/Users/dom/work/vercel/commerce/node_modules/@vercel/commerce/package.json'
}
error Command failed with exit code 1.
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.
In order to fix this, run `yarn dev` in the monorepo root folder first.
> Using `yarn dev` from the root is recommended for developing, which will run watch mode on all packages.
</details>

View File

@ -1,184 +0,0 @@
import { FC } from 'react'
import cn from 'classnames'
import {
CardNumberElement,
CardExpiryElement,
CardCvcElement,
useStripe,
useElements
} from '@stripe/react-stripe-js'
import { Button, Text } from '@components/ui'
import { useUI } from '@components/ui/context'
import s from './PaymentMethodView.module.css'
import SidebarLayout from '@components/common/SidebarLayout'
import countries from '@lib/countries'
const PaymentMethodView: FC = () => {
const { paymentMethodDetails, setPaymentMethodDetails, setSidebarView } = useUI()
const { address } = paymentMethodDetails
const stripe = useStripe()
const elements = useElements()
const handleSubmit = async () => {
if (!stripe || !elements) {
return
}
const card = elements.getElement(CardNumberElement)
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: 'card',
card
})
if (error) {
console.error(error)
}
setPaymentMethodDetails({ address, paymentMethod })
setSidebarView('CHECKOUT_VIEW')
}
const updateAddressData = ({ target }: any) => setPaymentMethodDetails({
...paymentMethodDetails,
address: {
...paymentMethodDetails.address,
[target.name]: target.value,
},
})
return (
<SidebarLayout handleBack={handleSubmit}>
<div className="px-4 sm:px-6 flex-1">
<Text variant="sectionHeading"> Payment Method</Text>
<div>
<div className={s.fieldset}>
<label className={s.label}>Cardholder Name</label>
<input
className={s.input}
name='cardholderName'
onChange={updateAddressData}
value={address.cardholderName}
/>
</div>
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-7')}>
<label className={s.label}>Card Number</label>
<CardNumberElement className={s.input} />
</div>
<div className={cn(s.fieldset, 'col-span-3')}>
<label className={s.label}>Expires</label>
<CardExpiryElement className={s.input} placeholder='MM/YY' />
</div>
<div className={cn(s.fieldset, 'col-span-2')}>
<label className={s.label}>CVC</label>
<CardCvcElement className={s.input} />
</div>
</div>
<hr className="border-accent-2 my-6" />
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>First Name</label>
<input
className={s.input}
name='firstName'
onChange={updateAddressData}
value={address.firstName}
/>
</div>
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>Last Name</label>
<input
className={s.input}
name='lastName'
onChange={updateAddressData}
value={address.lastName}
/>
</div>
</div>
<div className={s.fieldset}>
<label className={s.label}>Company (Optional)</label>
<input
className={s.input}
name='company'
onChange={updateAddressData}
value={address.company}
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Street and House Number</label>
<input
className={s.input}
name='addressLine1'
onChange={updateAddressData}
value={address.addressLine1}
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
<input
className={s.input}
name='addressLine2'
onChange={updateAddressData}
value={address.addressLine2}
/>
</div>
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>Postal Code</label>
<input
className={s.input}
name='postalCode'
onChange={updateAddressData}
value={address.postalCode}
/>
</div>
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>City</label>
<input
className={s.input}
name='city'
onChange={updateAddressData}
value={address.city}
/>
</div>
</div>
<div className={s.fieldset}>
<label className={s.label}>Region</label>
<input
className={s.input}
name='region'
onChange={updateAddressData}
value={address.region}
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Country</label>
<select
className={s.select}
name='country'
onChange={updateAddressData}
value={address.country}
>
{countries.map((country) => (
<option
key={country.code}
value={country.code}
>
{country.name}
</option>
))}
</select>
</div>
</div>
</div>
<div className="sticky z-20 bottom-0 w-full right-0 left-0 py-12 bg-accent-0 border-t border-accent-2 px-6">
<Button Component="a" width="100%" variant="ghost">
Continue
</Button>
</div>
</SidebarLayout>
)
}
export default PaymentMethodView

View File

@ -1,207 +0,0 @@
import { FC } from 'react'
import cn from 'classnames'
import s from './ShippingView.module.css'
import Button from '@components/ui/Button'
import { useUI } from '@components/ui/context'
import SidebarLayout from '@components/common/SidebarLayout'
import countries from '@lib/countries'
import useAddShippingAddress from '@framework/cart/use-add-shipping-address'
const PaymentMethodView: FC = () => {
const addShippingAddress = useAddShippingAddress()
const {
paymentMethodDetails,
setShippingAddress,
setSidebarView,
setUseBillingAddressForShipping,
shippingAddress,
useBillingAddressForShipping,
} = useUI()
const handleUseBillingAddressForShipping = (event) => {
setUseBillingAddressForShipping(event.target.value === 'true')
if (event.target.value === 'true') {
setShippingAddress({
firstName: paymentMethodDetails.address?.firstName,
lastName: paymentMethodDetails.address?.lastName,
company: paymentMethodDetails.address?.company,
addressLine1: paymentMethodDetails.address?.addressLine1,
addressLine2: paymentMethodDetails.address?.addressLine2,
postalCode: paymentMethodDetails.address?.postalCode,
city: paymentMethodDetails.address?.city,
country: paymentMethodDetails.address?.country,
})
}
}
const updateAddressData = ({ target }: any) => setShippingAddress({
...shippingAddress,
[target.name]: target.value,
})
return (
<SidebarLayout handleBack={async () => {
// add shipping address to cart
await addShippingAddress({ address: shippingAddress })
setSidebarView('CHECKOUT_VIEW')
}}>
<div className="px-4 sm:px-6 flex-1">
<h2 className="pt-1 pb-8 text-2xl font-semibold tracking-wide cursor-pointer inline-block">
Shipping
</h2>
<div>
<div className="flex flex-row my-3 items-center">
<input
className={s.radio}
type="radio"
id="useBillingAddressForShippingTrue"
name="useBillingAddressForShipping"
value="true"
onChange={handleUseBillingAddressForShipping}
checked={useBillingAddressForShipping === true}
/>
<label htmlFor="useBillingAddressForShippingTrue" className="ml-3 text-sm">Same as billing address</label>
</div>
<div className="flex flex-row my-3 items-center">
<input
className={s.radio}
type="radio"
id="useBillingAddressForShippingFalse"
name="useBillingAddressForShipping"
value="false"
onChange={handleUseBillingAddressForShipping}
checked={useBillingAddressForShipping === false}
/>
<label htmlFor="useBillingAddressForShippingFalse" className="ml-3 text-sm">
Use a different shipping address
</label>
</div>
<hr className="border-accent-2 my-6" />
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>First Name</label>
<input
className={s.input}
name='firstName'
onChange={updateAddressData}
value={shippingAddress.firstName}
disabled={useBillingAddressForShipping}
/>
</div>
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>Last Name</label>
<input
className={s.input}
name='lastName'
onChange={updateAddressData}
value={shippingAddress.lastName}
disabled={useBillingAddressForShipping}
/>
</div>
</div>
<div className={s.fieldset}>
<label className={s.label}>Phone Number</label>
<input
className={s.input}
name='phone'
onChange={updateAddressData}
value={shippingAddress.phone}
disabled={useBillingAddressForShipping}
type='tel'
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Company (Optional)</label>
<input
className={s.input}
name='company'
onChange={updateAddressData}
value={shippingAddress.company}
disabled={useBillingAddressForShipping}
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Street and House Number</label>
<input
className={s.input}
name='addressLine1'
onChange={updateAddressData}
value={shippingAddress.addressLine1}
disabled={useBillingAddressForShipping}
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
<input
className={s.input}
name='addressLine2'
onChange={updateAddressData}
value={shippingAddress.addressLine2}
disabled={useBillingAddressForShipping}
/>
</div>
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>Postal Code</label>
<input
className={s.input}
name='postalCode'
onChange={updateAddressData}
value={shippingAddress.postalCode}
disabled={useBillingAddressForShipping}
/>
</div>
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>City</label>
<input
className={s.input}
name='city'
onChange={updateAddressData}
value={shippingAddress.city}
disabled={useBillingAddressForShipping}
/>
</div>
</div>
<div className={s.fieldset}>
<label className={s.label}>Region</label>
<input
className={s.input}
name='region'
onChange={updateAddressData}
value={shippingAddress.region}
disabled={useBillingAddressForShipping}
/>
</div>
<div className={s.fieldset}>
<label className={s.label}>Country</label>
<select
className={s.select}
name="country"
onChange={updateAddressData}
value={shippingAddress.country}
disabled={useBillingAddressForShipping}
>
{countries.map((country) => (
<option
key={country.code}
value={country.code}
>
{country.name}
</option>
))}
</select>
</div>
</div>
</div>
<div className="sticky z-20 bottom-0 w-full right-0 left-0 py-12 bg-accent-0 border-t border-accent-2 px-6">
<Button Component="a" width="100%" variant="ghost">
Continue
</Button>
</div>
</SidebarLayout>
)
}
export default PaymentMethodView

View File

@ -1,7 +0,0 @@
.root {
@apply text-center p-6 bg-primary text-sm flex-row justify-center items-center font-medium fixed bottom-0 w-full z-30 transition-all duration-300 ease-out;
@screen md {
@apply flex text-left;
}
}

View File

@ -1,18 +0,0 @@
import { FC } from 'react'
import NextHead from 'next/head'
import { DefaultSeo } from 'next-seo'
import config from '@config/seo.json'
const Head: FC = () => {
return (
<>
<DefaultSeo {...config} />
<NextHead>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="/site.webmanifest" key="site-manifest" />
</NextHead>
</>
)
}
export default Head

View File

@ -1,63 +0,0 @@
import { FC, InputHTMLAttributes, useEffect, useMemo } from 'react'
import cn from 'classnames'
import s from './Searchbar.module.css'
import { useRouter } from 'next/router'
interface Props {
className?: string
id?: string
}
const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
const router = useRouter()
useEffect(() => {
router.prefetch('/search')
}, [])
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.preventDefault()
if (e.key === 'Enter') {
const q = e.currentTarget.value
router.push(
{
pathname: `/search`,
query: q ? { q } : {},
},
undefined,
{ shallow: true }
)
}
}
return useMemo(
() => (
<div className={cn(s.root, className)}>
<label className="hidden" htmlFor={id}>
Search
</label>
<input
id={id}
className={s.input}
placeholder="Search for products..."
defaultValue={router.query.q}
onKeyUp={handleKeyUp}
/>
<div className={s.iconContainer}>
<svg className={s.icon} fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
/>
</svg>
</div>
</div>
),
[]
)
}
export default Searchbar

View File

@ -1,50 +0,0 @@
import { Swatch } from '@components/product'
import type { ProductOption } from '@commerce/types/product'
import { SelectedOptions } from '../helpers'
import React from 'react'
interface ProductOptionsProps {
options: ProductOption[]
selectedOptions: SelectedOptions
setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>>
}
const ProductOptions: React.FC<ProductOptionsProps> = React.memo(
({ options, selectedOptions, setSelectedOptions }) => {
return (
<div>
{options.map((opt) => (
<div className="pb-4" key={opt.displayName}>
<h2 className="uppercase font-medium text-sm tracking-wide">
{opt.displayName}
</h2>
<div className="flex flex-row py-4">
{opt.values.map((v, i: number) => {
const active = selectedOptions[opt.displayName.toLowerCase()]
return (
<Swatch
key={`${opt.id}-${i}`}
active={v.label.toLowerCase() === active}
variant={opt.displayName}
color={v.hexColors ? v.hexColors[0] : ''}
label={v.label}
onClick={() => {
setSelectedOptions((selectedOptions) => {
return {
...selectedOptions,
[opt.displayName.toLowerCase()]:
v.label.toLowerCase(),
}
})
}}
/>
)
})}
</div>
</div>
))}
</div>
)
}
)
export default ProductOptions

View File

@ -1,31 +0,0 @@
import cn from 'classnames'
import React from 'react'
import s from './ProductSliderControl.module.css'
import { ArrowLeft, ArrowRight } from '@components/icons'
interface ProductSliderControl {
onPrev: React.MouseEventHandler<HTMLButtonElement>
onNext: React.MouseEventHandler<HTMLButtonElement>
}
const ProductSliderControl: React.FC<ProductSliderControl> = React.memo(
({ onPrev, onNext }) => (
<div className={s.control}>
<button
className={cn(s.leftControl)}
onClick={onPrev}
aria-label="Previous Product Image"
>
<ArrowLeft />
</button>
<button
className={cn(s.rightControl)}
onClick={onNext}
aria-label="Next Product Image"
>
<ArrowRight />
</button>
</div>
)
)
export default ProductSliderControl

View File

@ -1,62 +0,0 @@
import cn from 'classnames'
import React from 'react'
import s from './Swatch.module.css'
import { Check } from '@components/icons'
import Button, { ButtonProps } from '@components/ui/Button'
import { isDark } from '@lib/colors'
interface SwatchProps {
active?: boolean
children?: any
className?: string
variant?: 'size' | 'color' | string
color?: string
label?: string | null
}
const Swatch: React.FC<Omit<ButtonProps, 'variant'> & SwatchProps> = React.memo(
({
active,
className,
color = '',
label = null,
variant = 'size',
...props
}) => {
variant = variant?.toLowerCase()
if (label) {
label = label?.toLowerCase()
}
const swatchClassName = cn(
s.swatch,
{
[s.color]: color,
[s.active]: active,
[s.size]: variant === 'size',
[s.dark]: color ? isDark(color) : false,
[s.textLabel]: !color && label && label.length > 3,
},
className
)
return (
<Button
aria-label="Variant Swatch"
className={swatchClassName}
{...(label && color && { title: label })}
style={color ? { backgroundColor: color } : {}}
{...props}
>
{color && active && (
<span>
<Check />
</span>
)}
{!color ? label : null}
</Button>
)
}
)
export default Swatch

View File

@ -1,27 +0,0 @@
import React, { FC } from 'react'
import rangeMap from '@lib/range-map'
import { Star } from '@components/icons'
import cn from 'classnames'
export interface RatingProps {
value: number
}
const Quantity: React.FC<RatingProps> = React.memo(({ value = 5 }) => {
return (
<div className="flex flex-row py-6 text-accent-9">
{rangeMap(5, (i) => (
<span
key={`star_${i}`}
className={cn('inline-block ml-1 ', {
'text-accent-5': i >= Math.floor(value),
})}
>
<Star />
</span>
))}
</div>
)
})
export default Quantity

View File

@ -1,46 +0,0 @@
import { FC, useEffect, useRef } from 'react'
import s from './Sidebar.module.css'
import cn from 'classnames'
import {
disableBodyScroll,
enableBodyScroll,
clearAllBodyScrollLocks,
} from 'body-scroll-lock'
interface SidebarProps {
children: any
onClose: () => void
}
const Sidebar: FC<SidebarProps> = ({ children, onClose }) => {
const ref = useRef() as React.MutableRefObject<HTMLDivElement>
useEffect(() => {
if (ref.current) {
disableBodyScroll(ref.current, { reserveScrollBarGap: true })
}
return () => {
if (ref && ref.current) {
enableBodyScroll(ref.current)
}
clearAllBodyScrollLocks()
}
}, [])
return (
<div className={cn(s.root)}>
<div className="absolute inset-0 overflow-hidden">
<div className={s.backdrop} onClick={onClose} />
<section className="absolute inset-y-0 right-0 max-w-full flex outline-none pl-10">
<div className="h-full w-full md:w-screen md:max-w-md">
<div className={s.sidebar} ref={ref}>
{children}
</div>
</div>
</section>
</div>
</div>
)
}
export default Sidebar

View File

@ -1,15 +0,0 @@
.body {
@apply text-base leading-7 max-w-6xl mx-auto;
}
.heading {
@apply text-5xl pt-1 pb-2 font-semibold tracking-wide cursor-pointer mb-2;
}
.pageHeading {
@apply pt-1 pb-4 text-2xl leading-7 font-bold tracking-wide;
}
.sectionHeading {
@apply pt-1 pb-2 text-2xl font-bold tracking-wide cursor-pointer mb-2;
}

View File

@ -1,36 +0,0 @@
import type { ReactNode } from 'react'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { bigcommerceProvider } from './provider'
import type { BigcommerceProvider } from './provider'
export { bigcommerceProvider }
export type { BigcommerceProvider }
export const bigcommerceConfig: CommerceConfig = {
locale: 'en-us',
cartCookie: 'bc_cartId',
}
export type BigcommerceConfig = Partial<CommerceConfig>
export type BigcommerceProps = {
children?: ReactNode
locale: string
} & BigcommerceConfig
export function CommerceProvider({ children, ...config }: BigcommerceProps) {
return (
<CoreCommerceProvider
provider={bigcommerceProvider}
config={{ ...bigcommerceConfig, ...config }}
>
{children}
</CoreCommerceProvider>
)
}
export const useCommerce = () => useCoreCommerce<BigcommerceProvider>()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,89 +0,0 @@
/**
* This file is expected to be used in next.config.js only
*/
const path = require('path')
const fs = require('fs')
const merge = require('deepmerge')
const prettier = require('prettier')
const PROVIDERS = [
'bigcommerce',
'shopify',
'swell',
'vendure',
'reactioncommerce',
'saleor',
'local',
]
function getProviderName() {
return (
process.env.COMMERCE_PROVIDER ||
(process.env.BIGCOMMERCE_STOREFRONT_API_URL
? 'bigcommerce'
: process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN
? 'shopify'
: process.env.NEXT_PUBLIC_SWELL_STORE_ID
? 'swell'
: 'local')
)
}
function withCommerceConfig(nextConfig = {}) {
const commerce = nextConfig.commerce || {}
const name = commerce.provider || getProviderName()
if (!name) {
throw new Error(
`The commerce provider is missing, please add a valid provider name or its environment variables`
)
}
if (!PROVIDERS.includes(name)) {
throw new Error(
`The commerce provider "${name}" can't be found, please use one of "${PROVIDERS.join(
', '
)}"`
)
}
const commerceNextConfig = require(path.join('../', name, 'next.config'))
const config = merge(nextConfig, commerceNextConfig)
config.env = config.env || {}
Object.entries(config.commerce.features).forEach(([k, v]) => {
if (v) config.env[`COMMERCE_${k.toUpperCase()}_ENABLED`] = true
})
// Update paths in `tsconfig.json` to point to the selected provider
if (config.commerce.updateTSConfig !== false) {
const tsconfigPath = path.join(process.cwd(), 'tsconfig.json')
const tsconfig = require(tsconfigPath)
tsconfig.compilerOptions.paths['@framework'] = [`framework/${name}`]
tsconfig.compilerOptions.paths['@framework/*'] = [`framework/${name}/*`]
// When running for production it may be useful to exclude the other providers
// from TS checking
if (process.env.VERCEL) {
const exclude = tsconfig.exclude.filter(
(item) => !item.startsWith('framework/')
)
tsconfig.exclude = PROVIDERS.reduce((exclude, current) => {
if (current !== name) exclude.push(`framework/${current}`)
return exclude
}, exclude)
}
fs.writeFileSync(
tsconfigPath,
prettier.format(JSON.stringify(tsconfig), { parser: 'json' })
)
}
return config
}
module.exports = { withCommerceConfig, getProviderName }

View File

@ -1,10 +0,0 @@
export type CheckoutSchema = {
endpoint: {
options: {}
handlers: {
checkout: {
data: null
}
}
}
}

View File

@ -1,32 +0,0 @@
import * as React from 'react'
import { ReactNode } from 'react'
import { localProvider } from './provider'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
export const localConfig: CommerceConfig = {
locale: 'en-us',
cartCookie: 'session',
}
export function CommerceProvider({
children,
...config
}: {
children?: ReactNode
locale: string
} & Partial<CommerceConfig>) {
return (
<CoreCommerceProvider
provider={localProvider}
config={{ ...localConfig, ...config }}
>
{children}
</CoreCommerceProvider>
)
}
export const useCommerce = () => useCoreCommerce()

View File

@ -1,2 +0,0 @@
export * from '@commerce/product/use-price'
export { default } from '@commerce/product/use-price'

View File

@ -1,2 +0,0 @@
export * from '@commerce/product/use-price'
export { default } from '@commerce/product/use-price'

View File

@ -1 +0,0 @@
export default function (_commerce: any) {}

View File

@ -1,7 +0,0 @@
{
"provider": "saleor",
"features": {
"wishlist": false,
"customCheckout": true
}
}

View File

@ -1,32 +0,0 @@
import * as React from 'react'
import { ReactNode } from 'react'
import { CommerceConfig, CommerceProvider as CoreCommerceProvider, useCommerce as useCoreCommerce } from '@commerce'
import { saleorProvider, SaleorProvider } from './provider'
import * as Const from './const'
export { saleorProvider }
export type { SaleorProvider }
export const saleorConfig: CommerceConfig = {
locale: 'en-us',
cartCookie: Const.CHECKOUT_ID_COOKIE,
}
export type SaleorConfig = Partial<CommerceConfig>
export type SaleorProps = {
children?: ReactNode
locale: string
} & SaleorConfig
export function CommerceProvider({ children, ...config }: SaleorProps) {
return (
<CoreCommerceProvider provider={saleorProvider} config={{ ...saleorConfig, ...config }}>
{children}
</CoreCommerceProvider>
)
}
export const useCommerce = () => useCoreCommerce()

View File

@ -1,2 +0,0 @@
export * from '@commerce/product/use-price'
export { default } from '@commerce/product/use-price'

View File

@ -1 +0,0 @@
export default function (_commerce: any) {}

View File

@ -1,40 +0,0 @@
import * as React from 'react'
import { ReactNode } from 'react'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { shopifyProvider } from './provider'
import type { ShopifyProvider } from './provider'
import { SHOPIFY_CHECKOUT_ID_COOKIE } from './const'
export { shopifyProvider }
export type { ShopifyProvider }
export const shopifyConfig: CommerceConfig = {
locale: 'en-us',
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
}
export type ShopifyConfig = Partial<CommerceConfig>
export type ShopifyProps = {
children?: ReactNode
locale: string
} & ShopifyConfig
export function CommerceProvider({ children, ...config }: ShopifyProps) {
return (
<CoreCommerceProvider
provider={shopifyProvider}
config={{ ...shopifyConfig, ...config }}
>
{children}
</CoreCommerceProvider>
)
}
export const useCommerce = () => useCoreCommerce<ShopifyProvider>()

View File

@ -1,2 +0,0 @@
export * from '@commerce/product/use-price'
export { default } from '@commerce/product/use-price'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,33 +0,0 @@
import Cookies from 'js-cookie'
import {
SHOPIFY_CHECKOUT_ID_COOKIE,
SHOPIFY_CHECKOUT_URL_COOKIE,
SHOPIFY_COOKIE_EXPIRE,
} from '../const'
import checkoutCreateMutation from './mutations/checkout-create'
import { CheckoutCreatePayload } from '../schema'
export const checkoutCreate = async (
fetch: any
): Promise<CheckoutCreatePayload> => {
const data = await fetch({
query: checkoutCreateMutation,
})
const checkout = data.checkoutCreate?.checkout
const checkoutId = checkout?.id
if (checkoutId) {
const options = {
expires: SHOPIFY_COOKIE_EXPIRE,
}
Cookies.set(SHOPIFY_CHECKOUT_ID_COOKIE, checkoutId, options)
Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout.webUrl, options)
}
return checkout
}
export default checkoutCreate

View File

@ -1 +0,0 @@
export default function (_commerce: any) {}

View File

@ -1,47 +0,0 @@
import * as React from 'react'
import swell from 'swell-js'
import { ReactNode } from 'react'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { swellProvider, SwellProvider } from './provider'
import {
SWELL_CHECKOUT_ID_COOKIE,
SWELL_STORE_ID,
SWELL_PUBLIC_KEY,
} from './const'
swell.init(SWELL_STORE_ID, SWELL_PUBLIC_KEY)
export { swellProvider }
export type { SwellProvider }
export const swellConfig: any = {
locale: 'en-us',
cartCookie: SWELL_CHECKOUT_ID_COOKIE,
swell,
}
export type SwellConfig = Partial<CommerceConfig>
export type SwellProps = {
children?: ReactNode
locale: string
} & SwellConfig
export function CommerceProvider({ children, ...config }: SwellProps) {
return (
<CoreCommerceProvider
// TODO: Fix this type
provider={swellProvider as any}
config={{ ...swellConfig, ...config }}
>
{children}
</CoreCommerceProvider>
)
}
export const useCommerce = () => useCoreCommerce()

View File

@ -1,2 +0,0 @@
export * from '@commerce/product/use-price'
export { default } from '@commerce/product/use-price'

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +0,0 @@
import * as Core from '@commerce/types/login'
import { LoginBody, LoginTypes } from '@commerce/types/login'
export * from '@commerce/types/login'
export type LoginHook<T extends LoginTypes = LoginTypes> = {
data: null
actionInput: LoginBody
fetcherInput: LoginBody
body: T['body']
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,33 +0,0 @@
import * as React from 'react'
import { ReactNode } from 'react'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { vendureProvider } from './provider'
export const vendureConfig: CommerceConfig = {
locale: 'en-us',
cartCookie: 'session',
}
export type VendureConfig = Partial<CommerceConfig>
export type VendureProps = {
children?: ReactNode
locale: string
} & VendureConfig
export function CommerceProvider({ children, ...config }: VendureProps) {
return (
<CoreCommerceProvider
provider={vendureProvider}
config={{ ...vendureConfig, ...config }}
>
{children}
</CoreCommerceProvider>
)
}
export const useCommerce = () => useCoreCommerce()

View File

@ -1,2 +0,0 @@
export * from '@commerce/product/use-price'
export { default } from '@commerce/product/use-price'

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +0,0 @@
import * as Core from '@commerce/types/login'
import type { LoginMutationVariables } from '../schema'
import { LoginBody, LoginTypes } from '@commerce/types/login'
export * from '@commerce/types/login'
export type LoginHook<T extends LoginTypes = LoginTypes> = {
data: null
actionInput: LoginBody
fetcherInput: LoginBody
body: T['body']
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

3
next-env.d.ts vendored
View File

@ -1,3 +0,0 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />

View File

@ -1,121 +1,27 @@
{
"name": "nextjs-commerce",
"version": "1.0.0",
"scripts": {
"dev": "NODE_OPTIONS='--inspect' next dev -p 4000",
"build": "next build",
"start": "next start",
"analyze": "BUNDLE_ANALYZE=both yarn build",
"prettier-fix": "prettier --write .",
"find:unused": "npx next-unused",
"generate": "graphql-codegen",
"generate:reactioncommerce": "graphql-codegen --config framework/reactioncommerce/codegen.json",
"generate:shopify": "DOTENV_CONFIG_PATH=./.env.local graphql-codegen -r dotenv/config --config framework/shopify/codegen.json",
"generate:vendure": "graphql-codegen --config framework/vendure/codegen.json",
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js"
},
"sideEffects": false,
"name": "commerce",
"license": "MIT",
"engines": {
"node": ">=14.x"
},
"dependencies": {
"@react-spring/web": "^9.2.1",
"@stripe/react-stripe-js": "^1.4.1",
"@stripe/stripe-js": "^1.16.0",
"@vercel/fetch": "^6.1.0",
"autoprefixer": "^10.2.6",
"body-scroll-lock": "^3.1.5",
"classnames": "^2.3.1",
"cookie": "^0.4.1",
"email-validator": "^2.0.4",
"immutability-helper": "^3.1.1",
"js-cookie": "^2.2.1",
"keen-slider": "^5.5.1",
"lodash.debounce": "^4.0.8",
"lodash.random": "^3.2.0",
"lodash.throttle": "^4.1.1",
"next": "^11.0.0",
"next-seo": "^4.26.0",
"next-themes": "^0.0.14",
"postcss": "^8.3.5",
"postcss-nesting": "^8.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-fast-marquee": "^1.1.4",
"react-merge-refs": "^1.1.0",
"react-use-measure": "^2.0.4",
"swell-js": "^4.0.0-next.0",
"swr": "^0.5.6",
"tabbable": "^5.2.0",
"tailwindcss": "^2.2.2",
"uuidv4": "^6.2.10"
"private": true,
"workspaces": [
"site",
"packages/*"
],
"scripts": {
"build": "turbo run build --scope=next-commerce --include-dependencies --no-deps",
"dev": "turbo run dev",
"start": "turbo run start",
"types": "turbo run types",
"prettier-fix": "prettier --write ."
},
"devDependencies": {
"@graphql-codegen/cli": "^1.21.5",
"@graphql-codegen/schema-ast": "^1.18.3",
"@graphql-codegen/typescript": "^1.22.2",
"@graphql-codegen/typescript-operations": "^1.18.1",
"@next/bundle-analyzer": "^10.2.3",
"@types/body-scroll-lock": "^2.6.1",
"@types/cookie": "^0.4.0",
"@types/js-cookie": "^2.2.6",
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.random": "^3.2.6",
"@types/lodash.throttle": "^4.1.6",
"@types/node": "^15.12.4",
"@types/react": "^17.0.8",
"deepmerge": "^4.2.2",
"graphql": "^15.5.1",
"husky": "^6.0.0",
"lint-staged": "^11.0.0",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-preset-env": "^6.7.0",
"prettier": "^2.3.0",
"typescript": "4.3.4"
"husky": "^7.0.4",
"prettier": "^2.5.1",
"turbo": "^1.1.2"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
"pre-commit": "turbo run lint"
}
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"prettier --write",
"git add"
],
"**/*.{md,mdx,json}": [
"prettier --write",
"git add"
]
},
"next-unused": {
"alias": {
"@lib/*": [
"lib/*"
],
"@assets/*": [
"assets/*"
],
"@config/*": [
"config/*"
],
"@components/*": [
"components/*"
],
"@utils/*": [
"utils/*"
]
},
"debug": true,
"include": [
"components",
"lib",
"pages"
],
"exclude": [],
"entrypoints": [
"pages"
]
}
"packageManager": "yarn@1.22.17"
}

View File

@ -0,0 +1,2 @@
node_modules
dist

View File

@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false
}

View File

@ -9,7 +9,7 @@ With the deploy button below you'll be able to have a [BigCommerce](https://www.
If you already have a BigCommerce account and want to use your current store, then copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git):
```bash
cp framework/bigcommerce/.env.template .env.local
cp packages/bigcommerce/.env.template .env.local
```
Then, set the environment variables in `.env.local` to match the ones from your store.

View File

@ -8,16 +8,16 @@
},
"documents": [
{
"./framework/bigcommerce/api/**/*.ts": {
"./src/api/**/*.ts": {
"noRequire": true
}
}
],
"generates": {
"./framework/bigcommerce/schema.d.ts": {
"./schema.d.ts": {
"plugins": ["typescript", "typescript-operations"]
},
"./framework/bigcommerce/schema.graphql": {
"./schema.graphql": {
"plugins": ["schema-ast"]
}
},

View File

@ -0,0 +1,86 @@
{
"name": "@vercel/commerce-bigcommerce",
"version": "0.0.1",
"license": "MIT",
"scripts": {
"release": "taskr release",
"build": "taskr build",
"dev": "taskr",
"types": "tsc --emitDeclarationOnly",
"generate:definitions": "node scripts/generate-definitions.js"
},
"sideEffects": false,
"type": "module",
"exports": {
".": "./dist/index.js",
"./*": [
"./dist/*.js",
"./dist/*/index.js"
],
"./next.config": "./dist/next.config.cjs"
},
"typesVersions": {
"*": {
"*": [
"src/*",
"src/*/index"
],
"next.config": [
"dist/next.config.d.cts"
]
}
},
"files": [
"dist"
],
"publishConfig": {
"typesVersions": {
"*": {
"*": [
"dist/*.d.ts",
"dist/*/index.d.ts"
],
"config": [
"dist/next.config.d.cts"
]
}
}
},
"dependencies": {
"@vercel/fetch": "^6.1.1",
"cookie": "^0.4.1",
"immutability-helper": "^3.1.1",
"jsonwebtoken": "^8.5.1",
"lodash.debounce": "^4.0.8",
"uuidv4": "^6.2.12"
},
"peerDependencies": {
"next": "^12",
"react": "^17",
"react-dom": "^17"
},
"devDependencies": {
"@taskr/clear": "^1.1.0",
"@taskr/esnext": "^1.1.0",
"@taskr/watch": "^1.1.0",
"@types/cookie": "^0.4.1",
"@types/jsonwebtoken": "^8.5.7",
"@types/lodash.debounce": "^4.0.6",
"@types/node": "^17.0.8",
"@types/react": "^17.0.38",
"lint-staged": "^12.1.7",
"next": "^12.0.8",
"prettier": "^2.5.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"taskr": "^1.1.0",
"taskr-swc": "^0.0.1",
"typescript": "^4.5.4"
},
"lint-staged": {
"**/*.{js,jsx,ts,tsx,json}": [
"prettier --write",
"git add"
]
}
}

View File

@ -27,11 +27,11 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
}
const { data } = cartId
? await config.storeApiFetch(
`/v3/carts/${cartId}/items?include=line_items.physical_items.options`,
`/v3/carts/${cartId}/items?include=line_items.physical_items.options,line_items.digital_items.options`,
options
)
: await config.storeApiFetch(
'/v3/carts?include=line_items.physical_items.options',
'/v3/carts?include=line_items.physical_items.options,line_items.digital_items.options',
options
)

View File

@ -15,7 +15,7 @@ const getCart: CartEndpoint['handlers']['getCart'] = async ({
if (cartId) {
try {
result = await config.storeApiFetch(
`/v3/carts/${cartId}?include=line_items.physical_items.options`
`/v3/carts/${cartId}?include=line_items.physical_items.options,line_items.digital_items.options`
)
} catch (error) {
if (error instanceof BigcommerceApiError && error.status === 404) {

View File

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

View File

@ -1,4 +1,4 @@
import { Product } from '@commerce/types/product'
import { Product } from '@vercel/commerce/types/product'
import { ProductsEndpoint } from '.'
const SORT: { [key: string]: string | undefined } = {

View File

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

View File

@ -5,7 +5,7 @@ import { uuid } from 'uuidv4'
const fullCheckout = true
const checkout: CheckoutEndpoint['handlers']['checkout'] = async ({
const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
req,
res,
config,
@ -42,7 +42,7 @@ const checkout: CheckoutEndpoint['handlers']['checkout'] = async ({
store_hash: config.storeHash,
customer_id: customerId,
channel_id: config.storeChannelId,
redirect_to: data.checkout_url,
redirect_to: data.checkout_url.replace(config.storeUrl, ""),
}
let token = jwt.sign(payload, config.storeApiClientSecret!, {
algorithm: 'HS256',
@ -87,4 +87,4 @@ const checkout: CheckoutEndpoint['handlers']['checkout'] = async ({
res.end()
}
export default checkout
export default getCheckout

View File

@ -1,14 +1,14 @@
import { GetAPISchema, createEndpoint } from '@commerce/api'
import checkoutEndpoint from '@commerce/api/endpoints/checkout'
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
import checkoutEndpoint from '@vercel/commerce/api/endpoints/checkout'
import type { CheckoutSchema } from '../../../types/checkout'
import type { BigcommerceAPI } from '../..'
import checkout from './checkout'
import getCheckout from './get-checkout'
export type CheckoutAPI = GetAPISchema<BigcommerceAPI, CheckoutSchema>
export type CheckoutEndpoint = CheckoutAPI['endpoint']
export const handlers: CheckoutEndpoint['handlers'] = { checkout }
export const handlers: CheckoutEndpoint['handlers'] = { getCheckout }
const checkoutApi = createEndpoint<CheckoutAPI>({
handler: checkoutEndpoint,

View File

@ -1,4 +1,4 @@
import type { GetLoggedInCustomerQuery } from '../../../schema'
import type { GetLoggedInCustomerQuery } from '../../../../schema'
import type { CustomerEndpoint } from '.'
export const getLoggedInCustomerQuery = /* GraphQL */ `

View File

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

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