Merge pull request #1 from vercel/main

From vercel
This commit is contained in:
Gunasekaran R 2021-07-19 18:19:29 +05:30 committed by GitHub
commit 5270d5b9cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 592 additions and 12098 deletions

View File

@ -7,6 +7,10 @@ BIGCOMMERCE_STORE_API_URL=
BIGCOMMERCE_STORE_API_TOKEN= BIGCOMMERCE_STORE_API_TOKEN=
BIGCOMMERCE_STORE_API_CLIENT_ID= BIGCOMMERCE_STORE_API_CLIENT_ID=
BIGCOMMERCE_CHANNEL_ID= BIGCOMMERCE_CHANNEL_ID=
BIGCOMMERCE_STORE_URL=
BIGCOMMERCE_STORE_API_STORE_HASH=
BIGCOMMERCE_STORE_API_CLIENT_SECRET=
NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN= NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=
NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN= NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=

View File

@ -66,14 +66,20 @@ Every provider defines the features that it supports under `framework/{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.
For example: Turning `cart` off will disable Cart capabilities.
- cart
- search
- wishlist - wishlist
- customerAuth
- customCheckout - customCheckout
#### 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 the box)
- Open `commerce.config.json` - Open `commerce.config.json`
- You'll see a config file like this: - You'll see a config file like this:
```json ```json
{ {
@ -83,7 +89,7 @@ Every provider defines the features that it supports under `framework/{provider}
} }
} }
``` ```
- Turn wishlist on by setting wishlist to true. - Turn `wishlist` on by setting `wishlist` to `true`.
- Run the app and the wishlist functionality should be back on. - Run the app and the wishlist functionality should be back on.
### How to create a new provider ### How to create a new provider

View File

@ -1,6 +1,9 @@
{ {
"features": { "features": {
"cart": true,
"search": true,
"wishlist": false, "wishlist": false,
"customerAuth": false,
"customCheckout": false "customCheckout": false
} }
} }

View File

@ -1,5 +1,6 @@
.root { .root {
@apply sticky top-0 bg-primary z-40 transition-all duration-150; @apply sticky top-0 bg-primary z-40 transition-all duration-150;
min-height: 74px;
} }
.nav { .nav {

View File

@ -34,9 +34,11 @@ const Navbar: FC<NavbarProps> = ({ links }) => (
))} ))}
</nav> </nav>
</div> </div>
<div className="justify-center flex-1 hidden lg:flex"> {process.env.COMMERCE_SEARCH_ENABLED && (
<Searchbar /> <div className="justify-center flex-1 hidden lg:flex">
</div> <Searchbar />
</div>
)}
<div className="flex items-center justify-end flex-1 space-x-8"> <div className="flex items-center justify-end flex-1 space-x-8">
<UserNav /> <UserNav />
</div> </div>

View File

@ -25,10 +25,12 @@ const UserNav: FC<Props> = ({ className }) => {
return ( return (
<nav className={cn(s.root, className)}> <nav className={cn(s.root, className)}>
<ul className={s.list}> <ul className={s.list}>
<li className={s.item} onClick={toggleSidebar}> {process.env.COMMERCE_CART_ENABLED && (
<Bag /> <li className={s.item} onClick={toggleSidebar}>
{itemsCount > 0 && <span className={s.bagCount}>{itemsCount}</span>} <Bag />
</li> {itemsCount > 0 && <span className={s.bagCount}>{itemsCount}</span>}
</li>
)}
{process.env.COMMERCE_WISHLIST_ENABLED && ( {process.env.COMMERCE_WISHLIST_ENABLED && (
<li className={s.item}> <li className={s.item}>
<Link href="/wishlist"> <Link href="/wishlist">
@ -38,7 +40,7 @@ const UserNav: FC<Props> = ({ className }) => {
</Link> </Link>
</li> </li>
)} )}
{process.env.COMMERCE_CUSTOMER_ENABLED && ( {process.env.COMMERCE_CUSTOMERAUTH_ENABLED && (
<li className={s.item}> <li className={s.item}>
{customer ? ( {customer ? (
<DropdownMenu /> <DropdownMenu />

View File

@ -11,7 +11,7 @@ interface Props {
className?: string className?: string
product: Product product: Product
noNameTag?: boolean noNameTag?: boolean
imgProps?: Omit<ImageProps, 'src'> imgProps?: Omit<ImageProps, 'src' | 'layout' | 'placeholder' | 'blurDataURL'>
variant?: 'default' | 'slim' | 'simple' variant?: 'default' | 'slim' | 'simple'
} }
@ -83,7 +83,7 @@ const ProductCard: FC<Props> = ({
<Image <Image
alt={product.name || 'Product Image'} alt={product.name || 'Product Image'}
className={s.productImage} className={s.productImage}
src={product.images[0].url || placeholderImg} src={product.images[0]?.url || placeholderImg}
height={540} height={540}
width={540} width={540}
quality="85" quality="85"

View File

@ -56,18 +56,20 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ product, className }) => {
<div className="text-accent-6 pr-1 font-medium text-sm">36 reviews</div> <div className="text-accent-6 pr-1 font-medium text-sm">36 reviews</div>
</div> </div>
<div> <div>
<Button {process.env.COMMERCE_CART_ENABLED && (
aria-label="Add to Cart" <Button
type="button" aria-label="Add to Cart"
className={s.button} type="button"
onClick={addToCart} className={s.button}
loading={loading} onClick={addToCart}
disabled={variant?.availableForSale === false} loading={loading}
> disabled={variant?.availableForSale === false}
{variant?.availableForSale === false >
? 'Not Available' {variant?.availableForSale === false
: 'Add To Cart'} ? 'Not Available'
</Button> : 'Add To Cart'}
</Button>
)}
</div> </div>
<div className="mt-6"> <div className="mt-6">
<Collapse title="Care"> <Collapse title="Care">

View File

@ -1,4 +1,7 @@
import type { CheckoutEndpoint } from '.' import type { CheckoutEndpoint } from '.'
import getCustomerId from '../../utils/get-customer-id'
import jwt from 'jsonwebtoken'
import { uuid } from 'uuidv4'
const fullCheckout = true const fullCheckout = true
@ -9,22 +12,47 @@ const checkout: CheckoutEndpoint['handlers']['checkout'] = async ({
}) => { }) => {
const { cookies } = req const { cookies } = req
const cartId = cookies[config.cartCookie] const cartId = cookies[config.cartCookie]
const customerToken = cookies[config.customerCookie]
if (!cartId) { if (!cartId) {
res.redirect('/cart') res.redirect('/cart')
return return
} }
const { data } = await config.storeApiFetch( const { data } = await config.storeApiFetch(
`/v3/carts/${cartId}/redirect_urls`, `/v3/carts/${cartId}/redirect_urls`,
{ {
method: 'POST', method: 'POST',
} }
) )
const customerId =
customerToken && (await getCustomerId({ customerToken, config }))
if (fullCheckout) { //if there is a customer create a jwt token
res.redirect(data.checkout_url) if (!customerId) {
return if (fullCheckout) {
res.redirect(data.checkout_url)
return
}
} else {
const dateCreated = Math.round(new Date().getTime() / 1000)
const payload = {
iss: config.storeApiClientId,
iat: dateCreated,
jti: uuid(),
operation: 'customer_login',
store_hash: config.storeHash,
customer_id: customerId,
channel_id: config.storeChannelId,
redirect_to: data.checkout_url,
}
let token = jwt.sign(payload, config.storeApiClientSecret!, {
algorithm: 'HS256',
})
let checkouturl = `${config.storeUrl}/login/token/${token}`
console.log('checkouturl', checkouturl)
if (fullCheckout) {
res.redirect(checkouturl)
return
}
} }
// TODO: make the embedded checkout work too! // TODO: make the embedded checkout work too!

View File

@ -1,6 +1,6 @@
import getCustomerWishlist from '../../operations/get-customer-wishlist' import getCustomerWishlist from '../../operations/get-customer-wishlist'
import { parseWishlistItem } from '../../utils/parse-item' 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 // Return wishlist info

View File

@ -1,6 +1,6 @@
import type { Wishlist } from '../../../types/wishlist' import type { Wishlist } from '../../../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' import getCustomerWishlist from '../../operations/get-customer-wishlist'
// Return wishlist info // Return wishlist info

View File

@ -1,6 +1,6 @@
import type { Wishlist } from '../../../types/wishlist' import type { Wishlist } from '../../../types/wishlist'
import getCustomerWishlist from '../../operations/get-customer-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 '.'
// Return wishlist info // Return wishlist info

View File

@ -32,6 +32,9 @@ export interface BigcommerceConfig extends CommerceAPIConfig {
storeApiToken: string storeApiToken: string
storeApiClientId: string storeApiClientId: string
storeChannelId?: string storeChannelId?: string
storeUrl?: string
storeApiClientSecret?: string
storeHash?:string
storeApiFetch<T>(endpoint: string, options?: RequestInit): Promise<T> storeApiFetch<T>(endpoint: string, options?: RequestInit): Promise<T>
} }
@ -41,6 +44,9 @@ const STORE_API_URL = process.env.BIGCOMMERCE_STORE_API_URL
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
const STORE_URL = process.env.BIGCOMMERCE_STORE_URL
const CLIENT_SECRET = process.env.BIGCOMMERCE_STORE_API_CLIENT_SECRET
const STOREFRONT_HASH = process.env.BIGCOMMERCE_STORE_API_STORE_HASH
if (!API_URL) { if (!API_URL) {
throw new Error( throw new Error(
@ -75,6 +81,9 @@ const config: BigcommerceConfig = {
storeApiToken: STORE_API_TOKEN, storeApiToken: STORE_API_TOKEN,
storeApiClientId: STORE_API_CLIENT_ID, storeApiClientId: STORE_API_CLIENT_ID,
storeChannelId: STORE_CHANNEL_ID, storeChannelId: STORE_CHANNEL_ID,
storeUrl:STORE_URL,
storeApiClientSecret:CLIENT_SECRET,
storeHash: STOREFRONT_HASH,
storeApiFetch: createFetchStoreApi(() => getCommerceApi().getConfig()), storeApiFetch: createFetchStoreApi(() => getCommerceApi().getConfig()),
} }

View File

@ -1,5 +1,5 @@
import type { GetCustomerIdQuery } from '../../../../schema' import type { GetCustomerIdQuery } from '../../schema'
import type { BigcommerceConfig } from '../../..' import type { BigcommerceConfig } from '../'
export const getCustomerIdQuery = /* GraphQL */ ` export const getCustomerIdQuery = /* GraphQL */ `
query getCustomerId { query getCustomerId {

View File

@ -1,6 +1,7 @@
{ {
"provider": "bigcommerce", "provider": "bigcommerce",
"features": { "features": {
"wishlist": true "wishlist": true,
"customerAuth": true
} }
} }

View File

@ -57,11 +57,27 @@ function withCommerceConfig(nextConfig = {}) {
// Update paths in `tsconfig.json` to point to the selected provider // Update paths in `tsconfig.json` to point to the selected provider
if (config.commerce.updateTSConfig !== false) { if (config.commerce.updateTSConfig !== false) {
const staticTsconfigPath = path.join(process.cwd(), 'tsconfig.json') const tsconfigPath = path.join(process.cwd(), 'tsconfig.json')
const tsconfig = require('../../tsconfig.js') 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( fs.writeFileSync(
staticTsconfigPath, tsconfigPath,
prettier.format(JSON.stringify(tsconfig), { parser: 'json' }) prettier.format(JSON.stringify(tsconfig), { parser: 'json' })
) )
} }

View File

@ -1,4 +1,4 @@
import useSWR, { responseInterface } from 'swr' import useSWR, { SWRResponse } from 'swr'
import type { import type {
HookSWRInput, HookSWRInput,
HookFetchInput, HookFetchInput,
@ -11,7 +11,7 @@ import type {
import defineProperty from './define-property' import defineProperty from './define-property'
import { CommerceError } from './errors' import { CommerceError } from './errors'
export type ResponseState<Result> = responseInterface<Result, CommerceError> & { export type ResponseState<Result> = SWRResponse<Result, CommerceError> & {
isLoading: boolean isLoading: boolean
} }
@ -72,7 +72,7 @@ const useData: UseData = (options, input, fetcherFn, swrOptions) => {
}) })
} }
return response return response as typeof response & { isLoading: boolean }
} }
export default useData export default useData

View File

@ -1,6 +1,9 @@
{ {
"provider": "local", "provider": "local",
"features": { "features": {
"wishlist": false "wishlist": false,
"cart": false,
"search": false,
"customerAuth": false
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"schema": { "schema": {
"https://${NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN}/api/2021-01/graphql.json": { "https://${NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN}/api/2021-07/graphql.json": {
"headers": { "headers": {
"X-Shopify-Storefront-Access-Token": "${NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN}" "X-Shopify-Storefront-Access-Token": "${NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN}"
} }

1
next-env.d.ts vendored
View File

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

View File

@ -12,9 +12,6 @@ const isSwell = provider === 'swell'
const isVendure = provider === 'vendure' const isVendure = provider === 'vendure'
module.exports = withCommerceConfig({ module.exports = withCommerceConfig({
future: {
webpack5: true,
},
commerce, commerce,
i18n: { i18n: {
locales: ['en-US', 'es'], locales: ['en-US', 'es'],

11033
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
"start": "next start", "start": "next start",
"analyze": "BUNDLE_ANALYZE=both yarn build", "analyze": "BUNDLE_ANALYZE=both yarn build",
"prettier-fix": "prettier --write .", "prettier-fix": "prettier --write .",
"find:unused": "next-unused", "find:unused": "npx next-unused",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"generate:shopify": "DOTENV_CONFIG_PATH=./.env.local graphql-codegen -r dotenv/config --config framework/shopify/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:vendure": "graphql-codegen --config framework/vendure/codegen.json",
@ -23,39 +23,36 @@
"@vercel/fetch": "^6.1.0", "@vercel/fetch": "^6.1.0",
"autoprefixer": "^10.2.6", "autoprefixer": "^10.2.6",
"body-scroll-lock": "^3.1.5", "body-scroll-lock": "^3.1.5",
"bowser": "^2.11.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"cookie": "^0.4.1", "cookie": "^0.4.1",
"dot-object": "^2.1.4",
"email-validator": "^2.0.4", "email-validator": "^2.0.4",
"immutability-helper": "^3.1.1", "immutability-helper": "^3.1.1",
"isomorphic-unfetch": "^3.1.0",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"keen-slider": "^5.4.1", "keen-slider": "^5.5.1",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.random": "^3.2.0", "lodash.random": "^3.2.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"next": "10.0.9-canary.5", "next": "^11.0.0",
"next-seo": "^4.24.0", "next-seo": "^4.26.0",
"next-themes": "^0.0.14", "next-themes": "^0.0.14",
"postcss": "^8.3.0", "postcss": "^8.3.5",
"postcss-nesting": "^8.0.1", "postcss-nesting": "^8.0.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-fast-marquee": "^1.1.3", "react-fast-marquee": "^1.1.4",
"react-merge-refs": "^1.1.0", "react-merge-refs": "^1.1.0",
"react-use-measure": "^2.0.4", "react-use-measure": "^2.0.4",
"shopify-buy": "^2.11.0",
"swell-js": "^4.0.0-next.0", "swell-js": "^4.0.0-next.0",
"swr": "^0.5.6", "swr": "^0.5.6",
"tabbable": "^5.2.0", "tabbable": "^5.2.0",
"tailwindcss": "^2.1.2" "tailwindcss": "^2.2.2",
"uuidv4": "^6.2.10"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^1.21.5", "@graphql-codegen/cli": "^1.21.5",
"@graphql-codegen/schema-ast": "^1.18.3", "@graphql-codegen/schema-ast": "^1.18.3",
"@graphql-codegen/typescript": "^1.22.1", "@graphql-codegen/typescript": "^1.22.2",
"@graphql-codegen/typescript-operations": "^1.18.0", "@graphql-codegen/typescript-operations": "^1.18.1",
"@next/bundle-analyzer": "^10.2.3", "@next/bundle-analyzer": "^10.2.3",
"@types/body-scroll-lock": "^2.6.1", "@types/body-scroll-lock": "^2.6.1",
"@types/cookie": "^0.4.0", "@types/cookie": "^0.4.0",
@ -63,18 +60,16 @@
"@types/lodash.debounce": "^4.0.6", "@types/lodash.debounce": "^4.0.6",
"@types/lodash.random": "^3.2.6", "@types/lodash.random": "^3.2.6",
"@types/lodash.throttle": "^4.1.6", "@types/lodash.throttle": "^4.1.6",
"@types/node": "^15.6.1", "@types/node": "^15.12.4",
"@types/react": "^17.0.8", "@types/react": "^17.0.8",
"@types/shopify-buy": "^2.10.5",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"graphql": "^15.5.0", "graphql": "^15.5.1",
"husky": "^6.0.0", "husky": "^6.0.0",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",
"next-unused": "^0.0.6",
"postcss-flexbugs-fixes": "^5.0.2", "postcss-flexbugs-fixes": "^5.0.2",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",
"prettier": "^2.3.0", "prettier": "^2.3.0",
"typescript": "4.2.4" "typescript": "4.3.4"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View File

@ -1,57 +0,0 @@
const PROVIDERS = ['bigcommerce', 'shopify', 'swell', 'vendure', 'saleor']
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')
)
}
const name = getProviderName()
const EXCLUDED_PROVIDERS = PROVIDERS.filter((p) => p !== name).map(
(p) => `./framework/${p}`
)
module.exports = {
compilerOptions: {
baseUrl: '.',
target: 'esnext',
lib: ['dom', 'dom.iterable', 'esnext'],
allowJs: true,
skipLibCheck: true,
strict: true,
forceConsistentCasingInFileNames: true,
noEmit: true,
esModuleInterop: true,
module: 'esnext',
moduleResolution: 'node',
resolveJsonModule: true,
isolatedModules: true,
jsx: 'preserve',
paths: {
'@lib/*': ['lib/*'],
'@utils/*': ['utils/*'],
'@config/*': ['config/*'],
'@assets/*': ['assets/*'],
'@components/*': ['components/*'],
'@commerce': ['framework/commerce'],
'@commerce/*': ['framework/commerce/*'],
// Update paths to point to the selected provider
'@framework': [`framework/${name}`],
'@framework/*': [`framework/${name}/*`],
},
},
include: ['next-env.d.ts', '**/*.d.ts', '**/*.ts', '**/*.tsx', '**/*.js'],
exclude: [
'node_modules',
// It may be useful to exclude the other providers
// from TS checking
...EXCLUDED_PROVIDERS,
],
}

View File

@ -14,6 +14,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"incremental": true,
"paths": { "paths": {
"@lib/*": ["lib/*"], "@lib/*": ["lib/*"],
"@utils/*": ["utils/*"], "@utils/*": ["utils/*"],

1401
yarn.lock

File diff suppressed because it is too large Load Diff