Merge branch 'agnostic' of https://github.com/cond0r/commerce into shopify

This commit is contained in:
cond0r 2021-03-04 13:39:01 +02:00
commit 9f7a67e0d4
48 changed files with 707 additions and 378 deletions

6
.prettierrc Normal file
View File

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

View File

@ -7,7 +7,8 @@ Start right now at [nextjs.org/commerce](https://nextjs.org/commerce)
Demo live at: [demo.vercel.store](https://demo.vercel.store/) Demo live at: [demo.vercel.store](https://demo.vercel.store/)
This project is currently <b>under development</b>. - Shopify Demo: https://shopify.demo.vercel.store/
- BigCommerce Demo: https://bigcommerce.demo.vercel.store/
## Features ## Features
@ -21,34 +22,22 @@ This project is currently <b>under development</b>.
- Integrations - Integrate seamlessly with the most common ecommerce platforms. - Integrations - Integrate seamlessly with the most common ecommerce platforms.
- Dark Mode Support - Dark Mode Support
## 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)
## Integrations ## Integrations
Next.js Commerce integrates out-of-the-box with BigCommerce. We plan to support all major ecommerce backends. Next.js Commerce integrates out-of-the-box with BigCommerce and Shopify. We plan to support all major ecommerce backends.
## Goals ## Considerations
- **Next.js Commerce** should have a completely data **agnostic** UI - `framework/commerce` contains all types, helpers and functions to be used as base to build a new **provider**.
- **Aware of schema**: should ship with the right data schemas and types. - **Providers** live under `framework`'s root folder and they will extend Next.js Commerce types and functionality.
- All providers should return the right data types and schemas to blend correctly with Next.js Commerce. - **Features API** is 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.
- `@framework` will be the alias utilized in commerce and it will map to the ecommerce provider of preference- e.g BigCommerce, Shopify, Swell. All providers should expose the same standardized functions. _Note that the same applies for recipes using a CMS + an ecommerce provider._ - 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`
- We recommend that each **provider** ships with an `env.template` file and a `[readme.md](http://readme.md)` file.
There is a `framework` folder in the root folder that will contain multiple ecommerce providers. ## Provider Structure
Additionally, we need to ensure feature parity (not all providers have e.g. wishlist) we will also have to build a feature API to disable/enable features in the UI. Next.js Commerce provides a set of utilities and functions to create new providers. This is how a provider structure looks like.
People actively working on this project: @okbel & @lfades.
## Framework
Framework is where the data comes from. It contains mostly hooks and functions.
## Structure
Main folder and its exposed functions
- `product` - `product`
- usePrice - usePrice
@ -68,38 +57,72 @@ Main folder and its exposed functions
- getCustomerId - getCustomerId
- getCustomerWistlist - getCustomerWistlist
- `cart` - `cart`
- useCart - useCart
- useAddItem - useAddItem
- useRemoveItem - useRemoveItem
- useUpdateItem - useUpdateItem
- `env.template`
- `provider.ts`
- `commerce.config.json`
- `next.config.js`
- `README.md`
- `config.json` ## Configuration
- README.md
#### Example of correct usage of Commerce Framework ### How to change providers
```js First, update the provider selected in `commerce.config.json`:
import { useUI } from '@components/ui'
import { useCustomer } from '@framework/customer' ```json
import { useWishlist, useAddItem, useRemoveItem } from '@framework/wishlist' {
"provider": "bigcommerce",
"features": {
"wishlist": true
}
}
``` ```
## Config Then, change the paths defined in `tsconfig.json` and update the `@framework` paths to point to the right folder provider:
```json
"@framework": ["framework/bigcommerce"],
"@framework/*": ["framework/bigcommerce/*"]
```
Make sure to add the environment variables required by the new provider.
### Features ### Features
In order to make the UI entirely functional, we need to specify which features certain providers do not **provide**. Every provider defines the features that it supports under `framework/{provider}/commerce.config.json`
**Disabling wishlist:** #### 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)
- Open `commerce.config.json`
- You'll see a config file like this:
```json
{ {
"provider": "bigcommerce",
"features": { "features": {
"wishlist": false "wishlist": false
} }
} }
``` ```
- Turn wishlist on by setting wishlist to true.
- Run the app and the wishlist functionality should be back on.
### How to create a new provider
We'd recommend to duplicate a provider folder and push your providers SDK.
If you succeeded building a provider, submit a PR so we can all enjoy it.
## 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.
## Contribute ## Contribute

View File

@ -7,7 +7,7 @@
} }
.productDisplay { .productDisplay {
@apply relative flex px-0 pb-0 relative box-border col-span-1 bg-violet; @apply relative flex px-0 pb-0 box-border col-span-1 bg-violet;
min-height: 600px; min-height: 600px;
@screen md { @screen md {

View File

@ -1,4 +1,5 @@
.root { .root {
composes: root from 'components/ui/Button/Button.module.css';
@apply h-12 w-12 bg-primary text-primary rounded-full mr-3 inline-flex @apply h-12 w-12 bg-primary text-primary rounded-full mr-3 inline-flex
items-center justify-center cursor-pointer transition duration-150 ease-in-out items-center justify-center cursor-pointer transition duration-150 ease-in-out
p-0 shadow-none border-gray-200 border box-border; p-0 shadow-none border-gray-200 border box-border;

View File

@ -8,7 +8,8 @@ export type SelectedOptions = {
export function getVariant(product: Product, opts: SelectedOptions) { export function getVariant(product: Product, opts: SelectedOptions) {
const variant = product.variants.find((variant) => { const variant = product.variants.find((variant) => {
return Object.entries(opts).every(([key, value]) => return Object.entries(opts).every(([key, value]) =>
variant.options.find((option) => { value
? variant.options.find((option) => {
if ( if (
option.__typename === 'MultipleChoiceOption' && option.__typename === 'MultipleChoiceOption' &&
option.displayName.toLowerCase() === key.toLowerCase() option.displayName.toLowerCase() === key.toLowerCase()
@ -16,6 +17,9 @@ export function getVariant(product: Product, opts: SelectedOptions) {
return option.values.find((v) => v.label.toLowerCase() === value) return option.values.find((v) => v.label.toLowerCase() === value)
} }
}) })
: !variant.options.find(
(v) => v.displayName.toLowerCase() === key.toLowerCase()
)
) )
}) })
return variant return variant

View File

@ -1,12 +1,22 @@
{ {
"title": "ACME Storefront | Powered by Next.js Commerce", "title": "ACME Storefront | Powered by Next.js Commerce",
"titleTemplate": "%s - ACME Storefront", "titleTemplate": "%s - ACME Storefront",
"description": "Next.js Commerce -> https://www.nextjs.org/commerce", "description": "Next.js Commerce - https://www.nextjs.org/commerce",
"openGraph": { "openGraph": {
"title": "ACME Storefront | Powered by Next.js Commerce",
"description": "Next.js Commerce - https://www.nextjs.org/commerce",
"type": "website", "type": "website",
"locale": "en_IE", "locale": "en_IE",
"url": "https://nextjs.org/commerce", "url": "https://nextjs.org/commerce",
"site_name": "Next.js Commerce" "site_name": "Next.js Commerce",
"images": [
{
"url": "/card.png",
"width": 800,
"height": 600,
"alt": "Next.js Commerce"
}
]
}, },
"twitter": { "twitter": {
"handle": "@nextjs", "handle": "@nextjs",

View File

@ -7,6 +7,7 @@ const merge = require('deepmerge')
const PROVIDERS = ['bigcommerce', 'shopify'] const PROVIDERS = ['bigcommerce', 'shopify']
function getProviderName() { function getProviderName() {
// TODO: OSOT.
return process.env.BIGCOMMERCE_STOREFRONT_API_URL ? 'bigcommerce' : null return process.env.BIGCOMMERCE_STOREFRONT_API_URL ? 'bigcommerce' : null
} }

View File

@ -1,2 +1,2 @@
SHOPIFY_STORE_DOMAIN= NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN=
SHOPIFY_STOREFRONT_ACCESS_TOKEN= NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN=

View File

@ -1,21 +0,0 @@
import Client from 'shopify-buy'
import { ShopifyConfig } from '../index'
type Options = {
config: ShopifyConfig
}
const getAllCollections = async (options: Options) => {
const { config } = options
const client = Client.buildClient({
storefrontAccessToken: config.apiToken,
domain: config.commerceUrl,
})
const res = await client.collection.fetchAllWithProducts()
return JSON.parse(JSON.stringify(res))
}
export default getAllCollections

View File

@ -1,25 +0,0 @@
import { Page } from '../../schema'
import { ShopifyConfig, getConfig } from '..'
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
export type PageVariables = {
id: string
}
async function getPage({
url,
variables,
config,
preview,
}: {
url?: string
variables: PageVariables
config?: ShopifyConfig
preview?: boolean
}): Promise<GetPageResult> {
config = getConfig(config)
return {}
}
export default getPage

View File

@ -10,7 +10,7 @@ import {
MutationCheckoutCreateArgs, MutationCheckoutCreateArgs,
} from '../schema' } from '../schema'
import useLogin, { UseLogin } from '@commerce/auth/use-login' import useLogin, { UseLogin } from '@commerce/auth/use-login'
import { setCustomerToken } from '../utils' import { setCustomerToken, throwUserErrors } from '../utils'
export default useLogin as UseLogin<typeof handler> export default useLogin as UseLogin<typeof handler>
@ -45,13 +45,8 @@ export const handler: MutationHook<null, {}, CustomerAccessTokenCreateInput> = {
}, },
}) })
const errors = customerAccessTokenCreate?.customerUserErrors throwUserErrors(customerAccessTokenCreate?.customerUserErrors)
if (errors && errors.length) {
throw new ValidationError({
message: getErrorMessage(errors[0]),
})
}
const customerAccessToken = customerAccessTokenCreate?.customerAccessToken const customerAccessToken = customerAccessTokenCreate?.customerAccessToken
const accessToken = customerAccessToken?.accessToken const accessToken = customerAccessToken?.accessToken

View File

@ -1,15 +1,16 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types' import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError, ValidationError } from '@commerce/utils/errors'
import useSignup, { UseSignup } from '@commerce/auth/use-signup' import useSignup, { UseSignup } from '@commerce/auth/use-signup'
import useCustomer from '../customer/use-customer' import useCustomer from '../customer/use-customer'
import { CustomerCreateInput } from '../schema'
import { import {
customerCreateMutation, CustomerCreateInput,
customerAccessTokenCreateMutation, Mutation,
} from '../utils/mutations' MutationCustomerCreateArgs,
import handleLogin from '../utils/handle-login' } from '../schema'
import { customerCreateMutation } from '../utils/mutations'
import { handleAutomaticLogin, throwUserErrors } from '../utils'
export default useSignup as UseSignup<typeof handler> export default useSignup as UseSignup<typeof handler>
@ -33,7 +34,11 @@ export const handler: MutationHook<
'A first name, last name, email and password are required to signup', 'A first name, last name, email and password are required to signup',
}) })
} }
const data = await fetch({
const { customerCreate } = await fetch<
Mutation,
MutationCustomerCreateArgs
>({
...options, ...options,
variables: { variables: {
input: { input: {
@ -45,19 +50,10 @@ export const handler: MutationHook<
}, },
}) })
try { throwUserErrors(customerCreate?.customerUserErrors)
const loginData = await fetch({ await handleAutomaticLogin(fetch, { email, password })
query: customerAccessTokenCreateMutation,
variables: { return null
input: {
email,
password,
},
},
})
handleLogin(loginData)
} catch (error) {}
return data
}, },
useHook: ({ fetch }) => () => { useHook: ({ fetch }) => () => {
const { revalidate } = useCustomer() const { revalidate } = useCustomer()

View File

@ -1,3 +1,4 @@
export { default as useCart } from './use-cart' export { default as useCart } from './use-cart'
export { default as useAddItem } from './use-add-item' export { default as useAddItem } from './use-add-item'
export { default as useUpdateItem } from './use-update-item'
export { default as useRemoveItem } from './use-remove-item' export { default as useRemoveItem } from './use-remove-item'

View File

@ -1,12 +1,15 @@
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types' import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item' import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import useCart from './use-cart' import useCart from './use-cart'
import {
checkoutLineItemAddMutation,
getCheckoutId,
checkoutToCart,
} from '../utils'
import { Cart, CartItemBody } from '../types' import { Cart, CartItemBody } from '../types'
import { checkoutLineItemAddMutation, getCheckoutId } from '../utils'
import { checkoutToCart } from './utils'
import { Mutation, MutationCheckoutLineItemsAddArgs } from '../schema' import { Mutation, MutationCheckoutLineItemsAddArgs } from '../schema'
import { useCallback } from 'react'
export default useAddItem as UseAddItem<typeof handler> export default useAddItem as UseAddItem<typeof handler>
@ -40,8 +43,7 @@ export const handler: MutationHook<Cart, {}, CartItemBody> = {
}, },
}) })
// TODO: Fix this Cart type here return checkoutToCart(checkoutLineItemsAdd)
return checkoutToCart(checkoutLineItemsAdd) as any
}, },
useHook: ({ fetch }) => () => { useHook: ({ fetch }) => () => {
const { mutate } = useCart() const { mutate } = useCart()

View File

@ -6,7 +6,7 @@ import useCommerceCart, {
import { Cart } from '../types' import { Cart } from '../types'
import { SWRHook } from '@commerce/utils/types' import { SWRHook } from '@commerce/utils/types'
import { checkoutCreate, checkoutToCart } from './utils' import { checkoutCreate, checkoutToCart } from '../utils'
import getCheckoutQuery from '../utils/queries/get-checkout-query' import getCheckoutQuery from '../utils/queries/get-checkout-query'
export default useCommerceCart as UseCart<typeof handler> export default useCommerceCart as UseCart<typeof handler>
@ -22,6 +22,7 @@ export const handler: SWRHook<
}, },
async fetcher({ input: { cartId: checkoutId }, options, fetch }) { async fetcher({ input: { cartId: checkoutId }, options, fetch }) {
let checkout let checkout
if (checkoutId) { if (checkoutId) {
const data = await fetch({ const data = await fetch({
...options, ...options,
@ -36,8 +37,7 @@ export const handler: SWRHook<
checkout = await checkoutCreate(fetch) checkout = await checkoutCreate(fetch)
} }
// TODO: Fix this type return checkoutToCart({ checkout })
return checkoutToCart({ checkout } as any)
}, },
useHook: ({ useData }) => (input) => { useHook: ({ useData }) => (input) => {
const response = useData({ const response = useData({

View File

@ -1,23 +1,22 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { import type {
MutationHookContext, MutationHookContext,
HookFetcherContext, HookFetcherContext,
} from '@commerce/utils/types' } from '@commerce/utils/types'
import { RemoveCartItemBody } from '@commerce/types'
import { ValidationError } from '@commerce/utils/errors' import { ValidationError } from '@commerce/utils/errors'
import useRemoveItem, { import useRemoveItem, {
RemoveItemInput as RemoveItemInputBase, RemoveItemInput as RemoveItemInputBase,
UseRemoveItem, UseRemoveItem,
} from '@commerce/cart/use-remove-item' } from '@commerce/cart/use-remove-item'
import useCart from './use-cart' import useCart from './use-cart'
import { checkoutLineItemRemoveMutation, getCheckoutId } from '../utils' import {
import { checkoutToCart } from './utils' checkoutLineItemRemoveMutation,
getCheckoutId,
checkoutToCart,
} from '../utils'
import { Cart, LineItem } from '../types' import { Cart, LineItem } from '../types'
import { Mutation, MutationCheckoutLineItemsRemoveArgs } from '../schema' import { Mutation, MutationCheckoutLineItemsRemoveArgs } from '../schema'
import { RemoveCartItemBody } from '@commerce/types'
export type RemoveItemFn<T = any> = T extends LineItem export type RemoveItemFn<T = any> = T extends LineItem
? (input?: RemoveItemInput<T>) => Promise<Cart | null> ? (input?: RemoveItemInput<T>) => Promise<Cart | null>

View File

@ -13,7 +13,7 @@ import useUpdateItem, {
import useCart from './use-cart' import useCart from './use-cart'
import { handler as removeItemHandler } from './use-remove-item' import { handler as removeItemHandler } from './use-remove-item'
import type { Cart, LineItem, UpdateCartItemBody } from '../types' import type { Cart, LineItem, UpdateCartItemBody } from '../types'
import { checkoutToCart } from './utils' import { checkoutToCart } from '../utils'
import { getCheckoutId, checkoutLineItemUpdateMutation } from '../utils' import { getCheckoutId, checkoutLineItemUpdateMutation } from '../utils'
import { Mutation, MutationCheckoutLineItemsUpdateArgs } from '../schema' import { Mutation, MutationCheckoutLineItemsUpdateArgs } from '../schema'

View File

@ -1,42 +0,0 @@
import { Cart } from '../../types'
import { CommerceError, ValidationError } from '@commerce/utils/errors'
import {
CheckoutLineItemsAddPayload,
CheckoutLineItemsRemovePayload,
CheckoutLineItemsUpdatePayload,
Maybe,
} from '../../schema'
import { normalizeCart } from '../../utils'
export type CheckoutPayload =
| CheckoutLineItemsAddPayload
| CheckoutLineItemsUpdatePayload
| CheckoutLineItemsRemovePayload
const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
if (!checkoutPayload) {
throw new CommerceError({
message: 'Invalid response from Shopify',
})
}
const checkout = checkoutPayload?.checkout
const userErrors = checkoutPayload?.userErrors
if (userErrors && userErrors.length) {
throw new ValidationError({
message: userErrors[0].message,
})
}
if (!checkout) {
throw new CommerceError({
message: 'Invalid response from Shopify',
})
}
return normalizeCart(checkout)
}
export default checkoutToCart

View File

@ -1,31 +0,0 @@
import { HookFetcherFn } from '@commerce/utils/types'
import { Cart } from '@commerce/types'
import { checkoutCreate, checkoutToCart } from '.'
import { FetchCartInput } from '@commerce/cart/use-cart'
const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({
options,
input: { cartId: checkoutId },
fetch,
}) => {
let checkout
if (checkoutId) {
const data = await fetch({
...options,
variables: {
checkoutId,
},
})
checkout = data.node
}
if (checkout?.completedAt || !checkoutId) {
checkout = await checkoutCreate(fetch)
}
// TODO: Fix this type
return checkoutToCart({ checkout } as any)
}
export default fetcher

View File

@ -1,2 +0,0 @@
export { default as checkoutToCart } from './checkout-to-cart'
export { default as checkoutCreate } from './checkout-create'

View File

@ -48,7 +48,8 @@ export const handler: SWRHook<
edges = data.node?.products?.edges ?? [] edges = data.node?.products?.edges ?? []
if (brandId) { if (brandId) {
edges = edges.filter( edges = edges.filter(
({ node: { vendor } }: ProductEdge) => vendor === brandId ({ node: { vendor } }: ProductEdge) =>
vendor.replace(/\s+/g, '-').toLowerCase() === brandId
) )
} }
} else { } else {

View File

@ -23,9 +23,6 @@ export const shopifyProvider = {
customer: { useCustomer }, customer: { useCustomer },
products: { useSearch }, products: { useSearch },
auth: { useLogin, useLogout, useSignup }, auth: { useLogin, useLogout, useSignup },
features: {
wishlist: false,
},
} }
export type ShopifyProvider = typeof shopifyProvider export type ShopifyProvider = typeof shopifyProvider

View File

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

View File

@ -0,0 +1,48 @@
import { Cart } from '../types'
import { CommerceError } from '@commerce/utils/errors'
import {
CheckoutLineItemsAddPayload,
CheckoutLineItemsRemovePayload,
CheckoutLineItemsUpdatePayload,
CheckoutCreatePayload,
CheckoutUserError,
Checkout,
Maybe,
} from '../schema'
import { normalizeCart } from './normalize'
import throwUserErrors from './throw-user-errors'
export type CheckoutQuery = {
checkout: Checkout
checkoutUserErrors?: Array<CheckoutUserError>
}
export type CheckoutPayload =
| CheckoutLineItemsAddPayload
| CheckoutLineItemsUpdatePayload
| CheckoutLineItemsRemovePayload
| CheckoutCreatePayload
| CheckoutQuery
const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
if (!checkoutPayload) {
throw new CommerceError({
message: 'Missing checkout payload from response',
})
}
const checkout = checkoutPayload?.checkout
throwUserErrors(checkoutPayload?.checkoutUserErrors)
if (!checkout) {
throw new CommerceError({
message: 'Missing checkout object from response',
})
}
return normalizeCart(checkout)
}
export default checkoutToCart

View File

@ -1,4 +1,4 @@
const getSortVariables = (sort?: string, isCategory = false) => { const getSortVariables = (sort?: string, isCategory: boolean = false) => {
let output = {} let output = {}
switch (sort) { switch (sort) {
case 'price-asc': case 'price-asc':

View File

@ -2,13 +2,14 @@ import { ShopifyConfig } from '../api'
import fetchAllProducts from '../api/utils/fetch-all-products' import fetchAllProducts from '../api/utils/fetch-all-products'
import getAllProductVendors from './queries/get-all-product-vendors-query' import getAllProductVendors from './queries/get-all-product-vendors-query'
export type BrandNode = { export type Brand = {
entityId: string
name: string name: string
path: string path: string
} }
export type BrandEdge = { export type BrandEdge = {
node: BrandNode node: Brand
} }
export type Brands = BrandEdge[] export type Brands = BrandEdge[]
@ -24,13 +25,16 @@ const getVendors = async (config: ShopifyConfig): Promise<BrandEdge[]> => {
let vendorsStrings = vendors.map(({ node: { vendor } }) => vendor) let vendorsStrings = vendors.map(({ node: { vendor } }) => vendor)
return [...new Set(vendorsStrings)].map((v) => ({ return [...new Set(vendorsStrings)].map((v) => {
const id = v.replace(/\s+/g, '-').toLowerCase()
return {
node: { node: {
entityId: v, entityId: id,
name: v, name: v,
path: `brands/${v}`, path: `brands/${id}`,
}, },
})) }
})
} }
export default getVendors export default getVendors

View File

@ -0,0 +1,30 @@
import { FetcherOptions } from '@commerce/utils/types'
import throwUserErrors from './throw-user-errors'
import {
MutationCustomerActivateArgs,
MutationCustomerActivateByUrlArgs,
} from '../schema'
import { Mutation } from '../schema'
import { customerActivateByUrlMutation } from './mutations'
const handleAccountActivation = async (
fetch: <T = any, B = Body>(options: FetcherOptions<B>) => Promise<T>,
input: MutationCustomerActivateByUrlArgs
) => {
try {
const { customerActivateByUrl } = await fetch<
Mutation,
MutationCustomerActivateArgs
>({
query: customerActivateByUrlMutation,
variables: {
input,
},
})
throwUserErrors(customerActivateByUrl?.customerUserErrors)
} catch (error) {}
}
export default handleAccountActivation

View File

@ -1,30 +1,12 @@
import { ValidationError } from '@commerce/utils/errors' import { FetcherOptions } from '@commerce/utils/types'
import { CustomerAccessTokenCreateInput } from '../schema'
import { setCustomerToken } from './customer-token' import { setCustomerToken } from './customer-token'
import { customerAccessTokenCreateMutation } from './mutations'
const getErrorMessage = ({ import throwUserErrors from './throw-user-errors'
code,
message,
}: {
code: string
message: string
}) => {
switch (code) {
case 'UNIDENTIFIED_CUSTOMER':
message = 'Cannot find an account that matches the provided credentials'
break
}
return message
}
const handleLogin = (data: any) => { const handleLogin = (data: any) => {
const response = data.customerAccessTokenCreate const response = data.customerAccessTokenCreate
const errors = response?.customerUserErrors throwUserErrors(response?.customerUserErrors)
if (errors && errors.length) {
throw new ValidationError({
message: getErrorMessage(errors[0]),
})
}
const customerAccessToken = response?.customerAccessToken const customerAccessToken = response?.customerAccessToken
const accessToken = customerAccessToken?.accessToken const accessToken = customerAccessToken?.accessToken
@ -36,4 +18,19 @@ const handleLogin = (data: any) => {
return customerAccessToken return customerAccessToken
} }
export const handleAutomaticLogin = async (
fetch: <T = any, B = Body>(options: FetcherOptions<B>) => Promise<T>,
input: CustomerAccessTokenCreateInput
) => {
try {
const loginData = await fetch({
query: customerAccessTokenCreateMutation,
variables: {
input,
},
})
handleLogin(loginData)
} catch (error) {}
}
export default handleLogin export default handleLogin

View File

@ -4,6 +4,11 @@ export { default as getSortVariables } from './get-sort-variables'
export { default as getVendors } from './get-vendors' export { default as getVendors } from './get-vendors'
export { default as getCategories } from './get-categories' export { default as getCategories } from './get-categories'
export { default as getCheckoutId } from './get-checkout-id' export { default as getCheckoutId } from './get-checkout-id'
export { default as checkoutCreate } from './checkout-create'
export { default as checkoutToCart } from './checkout-to-cart'
export { default as handleLogin, handleAutomaticLogin } from './handle-login'
export { default as handleAccountActivation } from './handle-account-activation'
export { default as throwUserErrors } from './throw-user-errors'
export * from './queries' export * from './queries'
export * from './mutations' export * from './mutations'
export * from './normalize' export * from './normalize'

View File

@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query'
const checkoutCreateMutation = /* GraphQL */ ` const checkoutCreateMutation = /* GraphQL */ `
mutation { mutation {
checkoutCreate(input: {}) { checkoutCreate(input: {}) {
userErrors { checkoutUserErrors {
message code
field field
message
} }
checkout { checkout {
${checkoutDetailsFragment} ${checkoutDetailsFragment}

View File

@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query'
const checkoutLineItemAddMutation = /* GraphQL */ ` const checkoutLineItemAddMutation = /* GraphQL */ `
mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemInput!]!) { mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemInput!]!) {
checkoutLineItemsAdd(checkoutId: $checkoutId, lineItems: $lineItems) { checkoutLineItemsAdd(checkoutId: $checkoutId, lineItems: $lineItems) {
userErrors { checkoutUserErrors {
message code
field field
message
} }
checkout { checkout {
${checkoutDetailsFragment} ${checkoutDetailsFragment}

View File

@ -6,9 +6,10 @@ const checkoutLineItemRemoveMutation = /* GraphQL */ `
checkoutId: $checkoutId checkoutId: $checkoutId
lineItemIds: $lineItemIds lineItemIds: $lineItemIds
) { ) {
userErrors { checkoutUserErrors {
message code
field field
message
} }
checkout { checkout {
${checkoutDetailsFragment} ${checkoutDetailsFragment}

View File

@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query'
const checkoutLineItemUpdateMutation = /* GraphQL */ ` const checkoutLineItemUpdateMutation = /* GraphQL */ `
mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemUpdateInput!]!) { mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemUpdateInput!]!) {
checkoutLineItemsUpdate(checkoutId: $checkoutId, lineItems: $lineItems) { checkoutLineItemsUpdate(checkoutId: $checkoutId, lineItems: $lineItems) {
userErrors { checkoutUserErrors {
message code
field field
message
} }
checkout { checkout {
${checkoutDetailsFragment} ${checkoutDetailsFragment}

View File

@ -3,7 +3,7 @@ const customerAccessTokenDeleteMutation = /* GraphQL */ `
customerAccessTokenDelete(customerAccessToken: $customerAccessToken) { customerAccessTokenDelete(customerAccessToken: $customerAccessToken) {
deletedAccessToken deletedAccessToken
deletedCustomerAccessTokenId deletedCustomerAccessTokenId
userErrors { customerUserErrors {
field field
message message
} }

View File

@ -0,0 +1,19 @@
const customerActivateByUrlMutation = /* GraphQL */ `
mutation customerActivateByUrl($activationUrl: URL!, $password: String!) {
customerActivateByUrl(activationUrl: $activationUrl, password: $password) {
customer {
id
}
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}
`
export default customerActivateByUrlMutation

View File

@ -0,0 +1,19 @@
const customerActivateMutation = /* GraphQL */ `
mutation customerActivate($id: ID!, $input: CustomerActivateInput!) {
customerActivate(id: $id, input: $input) {
customer {
id
}
customerAccessToken {
accessToken
expiresAt
}
customerUserErrors {
code
field
message
}
}
}
`
export default customerActivateMutation

View File

@ -5,3 +5,5 @@ export { default as checkoutLineItemUpdateMutation } from './checkout-line-item-
export { default as checkoutLineItemRemoveMutation } from './checkout-line-item-remove' export { default as checkoutLineItemRemoveMutation } from './checkout-line-item-remove'
export { default as customerAccessTokenCreateMutation } from './customer-access-token-create' export { default as customerAccessTokenCreateMutation } from './customer-access-token-create'
export { default as customerAccessTokenDeleteMutation } from './customer-access-token-delete' export { default as customerAccessTokenDeleteMutation } from './customer-access-token-delete'
export { default as customerActivateMutation } from './customer-activate'
export { default as customerActivateByUrlMutation } from './customer-activate-by-url'

View File

@ -33,7 +33,7 @@ const normalizeProductOption = ({
let output: any = { let output: any = {
label: value, label: value,
} }
if (displayName === 'Color') { if (displayName.match(/colou?r/gi)) {
output = { output = {
...output, ...output,
hexColors: [value], hexColors: [value],
@ -54,21 +54,24 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
return edges?.map( return edges?.map(
({ ({
node: { id, selectedOptions, sku, title, priceV2, compareAtPriceV2 }, node: { id, selectedOptions, sku, title, priceV2, compareAtPriceV2 },
}) => ({ }) => {
return {
id, id,
name: title, name: title,
sku: sku ?? id, sku: sku ?? id,
price: +priceV2.amount, price: +priceV2.amount,
listPrice: +compareAtPriceV2?.amount, listPrice: +compareAtPriceV2?.amount,
requiresShipping: true, requiresShipping: true,
options: selectedOptions.map(({ name, value }: SelectedOption) => options: selectedOptions.map(({ name, value }: SelectedOption) => {
normalizeProductOption({ const options = normalizeProductOption({
id, id,
name, name,
values: [value], values: [value],
}) })
), return options
}) }),
}
}
) )
} }
@ -96,7 +99,12 @@ export function normalizeProduct(productNode: ShopifyProduct): Product {
price: money(priceRange?.minVariantPrice), price: money(priceRange?.minVariantPrice),
images: normalizeProductImages(images), images: normalizeProductImages(images),
variants: variants ? normalizeProductVariants(variants) : [], variants: variants ? normalizeProductVariants(variants) : [],
options: options ? options.map((o) => normalizeProductOption(o)) : [], options:
options
?.filter(({ name, values }) => {
return name !== 'Title' && values !== ['Default Title']
})
.map((o) => normalizeProductOption(o)) ?? [],
...rest, ...rest,
} }
@ -122,7 +130,7 @@ export function normalizeCart(checkout: Checkout): Cart {
} }
function normalizeLineItem({ function normalizeLineItem({
node: { id, title, variant, quantity }, node: { id, title, variant, quantity, ...rest },
}: CheckoutLineItemEdge): LineItem { }: CheckoutLineItemEdge): LineItem {
return { return {
id, id,
@ -135,7 +143,7 @@ function normalizeLineItem({
sku: variant?.sku ?? '', sku: variant?.sku ?? '',
name: variant?.title!, name: variant?.title!,
image: { image: {
url: variant?.image?.originalSrc, url: variant?.image?.originalSrc ?? '/product-img-placeholder.svg',
}, },
requiresShipping: variant?.requiresShipping ?? false, requiresShipping: variant?.requiresShipping ?? false,
price: variant?.priceV2?.amount, price: variant?.priceV2?.amount,
@ -143,10 +151,13 @@ function normalizeLineItem({
}, },
path: '', path: '',
discounts: [], discounts: [],
options: [ options:
variant?.title !== 'Default Title'
? [
{ {
value: variant?.title, value: variant?.title,
}, },
], ]
: [],
} }
} }

View File

@ -0,0 +1,38 @@
import { ValidationError } from '@commerce/utils/errors'
import {
CheckoutErrorCode,
CheckoutUserError,
CustomerErrorCode,
CustomerUserError,
} from '../schema'
export type UserErrors = Array<CheckoutUserError | CustomerUserError>
export type UserErrorCode =
| CustomerErrorCode
| CheckoutErrorCode
| null
| undefined
const getCustomMessage = (code: UserErrorCode, message: string) => {
switch (code) {
case 'UNIDENTIFIED_CUSTOMER':
message = 'Cannot find an account that matches the provided credentials'
break
}
return message
}
export const throwUserErrors = (errors?: UserErrors) => {
if (errors && errors.length) {
throw new ValidationError({
errors: errors.map(({ code, message }) => ({
code: code ?? 'validation_error',
message: getCustomMessage(code, message),
})),
})
}
}
export default throwUserErrors

View File

@ -1,13 +1,34 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/wishlist/use-add-item'
export function emptyHook() { import useCustomer from '../customer/use-customer'
const useEmptyHook = async (options = {}) => { import useWishlist from './use-wishlist'
return useCallback(async function () {
return Promise.resolve() export default useAddItem as UseAddItem<typeof handler>
}, [])
export const handler: MutationHook<any, {}, any, any> = {
fetchOptions: {
query: '',
},
useHook: ({ fetch }) => () => {
const { data: customer } = useCustomer()
const { revalidate } = useWishlist()
return useCallback(
async function addItem(item) {
if (!customer) {
// A signed customer is required in order to have a wishlist
throw new CommerceError({
message: 'Signed customer not found',
})
} }
return useEmptyHook await revalidate()
return null
},
[fetch, revalidate, customer]
)
},
} }
export default emptyHook

View File

@ -1,17 +1,36 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useRemoveItem, {
UseRemoveItem,
} from '@commerce/wishlist/use-remove-item'
type Options = { import useCustomer from '../customer/use-customer'
includeProducts?: boolean import useWishlist from './use-wishlist'
export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler: MutationHook<any, {}, any, any> = {
fetchOptions: {
query: '',
},
useHook: ({ fetch }) => () => {
const { data: customer } = useCustomer()
const { revalidate } = useWishlist()
return useCallback(
async function addItem(item) {
if (!customer) {
// A signed customer is required in order to have a wishlist
throw new CommerceError({
message: 'Signed customer not found',
})
} }
export function emptyHook(options?: Options) { await revalidate()
const useEmptyHook = async ({ id }: { id: string | number }) => { return null
return useCallback(async function () { },
return Promise.resolve() [fetch, revalidate, customer]
}, []) )
},
} }
return useEmptyHook
}
export default emptyHook

View File

@ -1,46 +1,49 @@
// TODO: replace this hook and other wishlist hooks with a handler, or remove them if import { useMemo } from 'react'
// Shopify doesn't have a wishlist import { SWRHook } from '@commerce/utils/types'
import useWishlist, { UseWishlist } from '@commerce/wishlist/use-wishlist'
import useCustomer from '../customer/use-customer'
import { HookFetcher } from '@commerce/utils/types' export type UseWishlistInput = { includeProducts?: boolean }
import { Product } from '../schema'
const defaultOpts = {} export default useWishlist as UseWishlist<typeof handler>
export type Wishlist = { export const handler: SWRHook<
items: [ any | null,
{ UseWishlistInput,
product_id: number { customerId?: number } & UseWishlistInput,
variant_id: number { isEmpty?: boolean }
id: number > = {
product: Product fetchOptions: {
url: '/api/bigcommerce/wishlist',
method: 'GET',
},
fetcher() {
return { items: [] }
},
useHook: ({ useData }) => (input) => {
const { data: customer } = useCustomer()
const response = useData({
input: [
['customerId', customer?.entityId],
['includeProducts', input?.includeProducts],
],
swrOptions: {
revalidateOnFocus: false,
...input?.swrOptions,
},
})
return useMemo(
() =>
Object.create(response, {
isEmpty: {
get() {
return (response.data?.items?.length || 0) <= 0
},
enumerable: true,
},
}),
[response]
)
},
} }
]
}
export interface UseWishlistOptions {
includeProducts?: boolean
}
export interface UseWishlistInput extends UseWishlistOptions {
customerId?: number
}
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = () => {
return null
}
export function extendHook(
customFetcher: typeof fetcher,
// swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
swrOptions?: any
) {
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
return { data: null }
}
useWishlist.extend = extendHook
return useWishlist
}
export default extendHook(fetcher)

View File

@ -42,9 +42,161 @@ function hexToRgb(hex: string = '') {
return [r, g, b] return [r, g, b]
} }
export function isDark(color = '') { const colorMap: Record<string, string> = {
aliceblue: '#F0F8FF',
antiquewhite: '#FAEBD7',
aqua: '#00FFFF',
aquamarine: '#7FFFD4',
azure: '#F0FFFF',
beige: '#F5F5DC',
bisque: '#FFE4C4',
black: '#000000',
blanchedalmond: '#FFEBCD',
blue: '#0000FF',
blueviolet: '#8A2BE2',
brown: '#A52A2A',
burlywood: '#DEB887',
cadetblue: '#5F9EA0',
chartreuse: '#7FFF00',
chocolate: '#D2691E',
coral: '#FF7F50',
cornflowerblue: '#6495ED',
cornsilk: '#FFF8DC',
crimson: '#DC143C',
cyan: '#00FFFF',
darkblue: '#00008B',
darkcyan: '#008B8B',
darkgoldenrod: '#B8860B',
darkgray: '#A9A9A9',
darkgreen: '#006400',
darkgrey: '#A9A9A9',
darkkhaki: '#BDB76B',
darkmagenta: '#8B008B',
darkolivegreen: '#556B2F',
darkorange: '#FF8C00',
darkorchid: '#9932CC',
darkred: '#8B0000',
darksalmon: '#E9967A',
darkseagreen: '#8FBC8F',
darkslateblue: '#483D8B',
darkslategray: '#2F4F4F',
darkslategrey: '#2F4F4F',
darkturquoise: '#00CED1',
darkviolet: '#9400D3',
deeppink: '#FF1493',
deepskyblue: '#00BFFF',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1E90FF',
firebrick: '#B22222',
floralwhite: '#FFFAF0',
forestgreen: '#228B22',
fuchsia: '#FF00FF',
gainsboro: '#DCDCDC',
ghostwhite: '#F8F8FF',
gold: '#FFD700',
goldenrod: '#DAA520',
gray: '#808080',
green: '#008000',
greenyellow: '#ADFF2F',
grey: '#808080',
honeydew: '#F0FFF0',
hotpink: '#FF69B4',
indianred: '#CD5C5C',
indigo: '#4B0082',
ivory: '#FFFFF0',
khaki: '#F0E68C',
lavender: '#E6E6FA',
lavenderblush: '#FFF0F5',
lawngreen: '#7CFC00',
lemonchiffon: '#FFFACD',
lightblue: '#ADD8E6',
lightcoral: '#F08080',
lightcyan: '#E0FFFF',
lightgoldenrodyellow: '#FAFAD2',
lightgray: '#D3D3D3',
lightgreen: '#90EE90',
lightgrey: '#D3D3D3',
lightpink: '#FFB6C1',
lightsalmon: '#FFA07A',
lightseagreen: '#20B2AA',
lightskyblue: '#87CEFA',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#B0C4DE',
lightyellow: '#FFFFE0',
lime: '#00FF00',
limegreen: '#32CD32',
linen: '#FAF0E6',
magenta: '#FF00FF',
maroon: '#800000',
mediumaquamarine: '#66CDAA',
mediumblue: '#0000CD',
mediumorchid: '#BA55D3',
mediumpurple: '#9370DB',
mediumseagreen: '#3CB371',
mediumslateblue: '#7B68EE',
mediumspringgreen: '#00FA9A',
mediumturquoise: '#48D1CC',
mediumvioletred: '#C71585',
midnightblue: '#191970',
mintcream: '#F5FFFA',
mistyrose: '#FFE4E1',
moccasin: '#FFE4B5',
navajowhite: '#FFDEAD',
navy: '#000080',
oldlace: '#FDF5E6',
olive: '#808000',
olivedrab: '#6B8E23',
orange: '#FFA500',
orangered: '#FF4500',
orchid: '#DA70D6',
palegoldenrod: '#EEE8AA',
palegreen: '#98FB98',
paleturquoise: '#AFEEEE',
palevioletred: '#DB7093',
papayawhip: '#FFEFD5',
peachpuff: '#FFDAB9',
peru: '#CD853F',
pink: '#FFC0CB',
plum: '#DDA0DD',
powderblue: '#B0E0E6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#FF0000',
rosybrown: '#BC8F8F',
royalblue: '#4169E1',
saddlebrown: '#8B4513',
salmon: '#FA8072',
sandybrown: '#F4A460',
seagreen: '#2E8B57',
seashell: '#FFF5EE',
sienna: '#A0522D',
silver: '#C0C0C0',
skyblue: '#87CEEB',
slateblue: '#6A5ACD',
slategray: '#708090',
slategrey: '#708090',
snow: '#FFFAFA',
springgreen: '#00FF7F',
steelblue: '#4682B4',
tan: '#D2B48C',
teal: '#008080',
thistle: '#D8BFD8',
tomato: '#FF6347',
turquoise: '#40E0D0',
violet: '#EE82EE',
wheat: '#F5DEB3',
white: '#FFFFFF',
whitesmoke: '#F5F5F5',
yellow: '#FFFF00',
yellowgreen: '#9ACD32',
}
export function isDark(color: string = ''): boolean {
color = color.toLowerCase()
// Equation from http://24ways.org/2010/calculating-color-contrast // Equation from http://24ways.org/2010/calculating-color-contrast
const rgb = hexToRgb(color) let rgb = colorMap[color] ? hexToRgb(colorMap[color]) : hexToRgb(color)
const res = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000 const res = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000
return res < 128 return res < 128
} }

View File

@ -14,15 +14,12 @@
"sideEffects": false, "sideEffects": false,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "12.x" "node": "14.x"
},
"prettier": {
"semi": false,
"singleQuote": true
}, },
"dependencies": { "dependencies": {
"@reach/portal": "^0.11.2", "@reach/portal": "^0.11.2",
"@vercel/fetch": "^6.1.0", "@vercel/fetch": "^6.1.0",
"autoprefixer": "^10.2.4",
"body-scroll-lock": "^3.1.5", "body-scroll-lock": "^3.1.5",
"bowser": "^2.11.0", "bowser": "^2.11.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
@ -38,7 +35,7 @@
"next": "^10.0.7", "next": "^10.0.7",
"next-seo": "^4.11.0", "next-seo": "^4.11.0",
"next-themes": "^0.0.4", "next-themes": "^0.0.4",
"postcss": "^8.2.4", "postcss": "^8.2.6",
"postcss-nesting": "^7.0.1", "postcss-nesting": "^7.0.1",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
@ -47,7 +44,7 @@
"shopify-buy": "^2.11.0", "shopify-buy": "^2.11.0",
"swr": "^0.4.0", "swr": "^0.4.0",
"tabbable": "^5.1.5", "tabbable": "^5.1.5",
"tailwindcss": "^2.0.2" "tailwindcss": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/cli": "^1.20.0", "@graphql-codegen/cli": "^1.20.0",
@ -73,7 +70,7 @@
"next-unused": "^0.0.3", "next-unused": "^0.0.3",
"postcss-flexbugs-fixes": "^4.2.1", "postcss-flexbugs-fixes": "^4.2.1",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",
"prettier": "^2.1.2", "prettier": "^2.2.1",
"typescript": "^4.0.3" "typescript": "^4.0.3"
}, },
"resolutions": { "resolutions": {

View File

@ -0,0 +1,26 @@
import type { GetStaticPropsContext } from 'next'
import { getConfig } from '@framework/api'
import getAllPages from '@framework/common/get-all-pages'
import { Layout } from '@components/common'
import { Container, Text } from '@components/ui'
export async function getStaticProps({
preview,
locale,
}: GetStaticPropsContext) {
const config = getConfig({ locale })
const { pages } = await getAllPages({ config, preview })
return {
props: { pages },
}
}
export default function ActivateAccount() {
return (
<Container>
<Text variant="pageHeading">Activate Your Account</Text>
</Container>
)
}
ActivateAccount.Layout = Layout

View File

@ -75,10 +75,8 @@ export default function Search({
const { data } = useSearch({ const { data } = useSearch({
search: typeof q === 'string' ? q : '', search: typeof q === 'string' ? q : '',
// TODO: Shopify - Fix this type categoryId: activeCategory?.entityId,
categoryId: activeCategory?.entityId as any, brandId: activeBrand?.entityId,
// TODO: Shopify - Fix this type
brandId: (activeBrand as any)?.entityId,
sort: typeof sort === 'string' ? sort : '', sort: typeof sort === 'string' ? sort : '',
}) })

BIN
public/card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -1609,6 +1609,18 @@ auto-bind@~4.0.0:
resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb" resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb"
integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ== integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==
autoprefixer@^10.2.4:
version "10.2.4"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.4.tgz#c0e7cf24fcc6a1ae5d6250c623f0cb8beef2f7e1"
integrity sha512-DCCdUQiMD+P/as8m3XkeTUkUKuuRqLGcwD0nll7wevhqoJfMRpJlkFd1+MQh1pvupjiQuip42lc/VFvfUTMSKw==
dependencies:
browserslist "^4.16.1"
caniuse-lite "^1.0.30001181"
colorette "^1.2.1"
fraction.js "^4.0.13"
normalize-range "^0.1.2"
postcss-value-parser "^4.1.0"
autoprefixer@^9.6.1: autoprefixer@^9.6.1:
version "9.8.6" version "9.8.6"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f"
@ -1818,7 +1830,7 @@ browserslist@4.16.1:
escalade "^3.1.1" escalade "^3.1.1"
node-releases "^1.1.69" node-releases "^1.1.69"
browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.6.4: browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.1, browserslist@^4.6.4:
version "4.16.3" version "4.16.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw== integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
@ -3187,6 +3199,11 @@ form-data@^3.0.0:
combined-stream "^1.0.8" combined-stream "^1.0.8"
mime-types "^2.1.12" mime-types "^2.1.12"
fraction.js@^4.0.13:
version "4.0.13"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe"
integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA==
fs-capacitor@^6.1.0: fs-capacitor@^6.1.0:
version "6.2.0" version "6.2.0"
resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-6.2.0.tgz#fa79ac6576629163cb84561995602d8999afb7f5" resolved "https://registry.yarnpkg.com/fs-capacitor/-/fs-capacitor-6.2.0.tgz#fa79ac6576629163cb84561995602d8999afb7f5"
@ -5586,7 +5603,7 @@ postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.
source-map "^0.6.1" source-map "^0.6.1"
supports-color "^6.1.0" supports-color "^6.1.0"
postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.4: postcss@^8.1.6, postcss@^8.2.1, postcss@^8.2.6:
version "8.2.6" version "8.2.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe"
integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg== integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg==
@ -5645,7 +5662,7 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
prettier@^2.0.5, prettier@^2.1.2: prettier@^2.0.5, prettier@^2.2.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
@ -6617,7 +6634,7 @@ tabbable@^5.1.5:
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.1.5.tgz#efec48ede268d511c261e3b81facbb4782a35147" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.1.5.tgz#efec48ede268d511c261e3b81facbb4782a35147"
integrity sha512-oVAPrWgLLqrbvQE8XqcU7CVBq6SQbaIbHkhOca3u7/jzuQvyZycrUKPCGr04qpEIUslmUlULbSeN+m3QrKEykA== integrity sha512-oVAPrWgLLqrbvQE8XqcU7CVBq6SQbaIbHkhOca3u7/jzuQvyZycrUKPCGr04qpEIUslmUlULbSeN+m3QrKEykA==
tailwindcss@^2.0.2: tailwindcss@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.0.3.tgz#f8d07797d1f89dc4b171673c26237b58783c2c86" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.0.3.tgz#f8d07797d1f89dc4b171673c26237b58783c2c86"
integrity sha512-s8NEqdLBiVbbdL0a5XwTb8jKmIonOuI4RMENEcKLR61jw6SdKvBss7NWZzwCaD+ZIjlgmesv8tmrjXEp7C0eAQ== integrity sha512-s8NEqdLBiVbbdL0a5XwTb8jKmIonOuI4RMENEcKLR61jw6SdKvBss7NWZzwCaD+ZIjlgmesv8tmrjXEp7C0eAQ==