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

This commit is contained in:
cond0r 2021-03-02 16:56:40 +02:00
commit 33ec99c701
36 changed files with 468 additions and 220 deletions

6
.prettierrc Normal file
View File

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

View File

@ -1,3 +1,7 @@
{ {
"provider": "shopify" "provider": "bigcommerce",
"features": {
"wishlist": true,
"customCheckout": false
}
} }

View File

@ -1,14 +1,14 @@
import { FC } from 'react' import { FC } from 'react'
import cn from 'classnames' import cn from 'classnames'
import { UserNav } from '@components/common' import Link from 'next/link'
import { Button } from '@components/ui'
import { Bag, Cross, Check } from '@components/icons'
import { useUI } from '@components/ui/context'
import useCart from '@framework/cart/use-cart'
import usePrice from '@framework/product/use-price'
import CartItem from '../CartItem' import CartItem from '../CartItem'
import s from './CartSidebarView.module.css' import s from './CartSidebarView.module.css'
import { LineItem } from '@commerce/types' import { Button } from '@components/ui'
import { UserNav } from '@components/common'
import { useUI } from '@components/ui/context'
import { Bag, Cross, Check } from '@components/icons'
import useCart from '@framework/cart/use-cart'
import usePrice from '@framework/product/use-price'
const CartSidebarView: FC = () => { const CartSidebarView: FC = () => {
const { closeSidebar } = useUI() const { closeSidebar } = useUI()
@ -88,9 +88,14 @@ const CartSidebarView: FC = () => {
) : ( ) : (
<> <>
<div className="px-4 sm:px-6 flex-1"> <div className="px-4 sm:px-6 flex-1">
<h2 className="pt-1 pb-4 text-2xl leading-7 font-bold text-base tracking-wide"> <Link href="/cart">
<h2
className="pt-1 pb-4 text-2xl leading-7 font-bold text-base tracking-wide cursor-pointer inline-block"
onClick={handleClose}
>
My Cart My Cart
</h2> </h2>
</Link>
<ul className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accents-3 border-t border-accents-3"> <ul className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accents-3 border-t border-accents-3">
{data!.lineItems.map((item: any) => ( {data!.lineItems.map((item: any) => (
<CartItem <CartItem

View File

@ -0,0 +1,20 @@
const CreditCard = ({ ...props }) => {
return (
<svg
viewBox="0 0 24 24"
width="24"
height="24"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
shapeRendering="geometricPrecision"
>
<rect x="1" y="4" width="22" height="16" rx="2" ry="2" />
<path d="M1 10h22" />
</svg>
)
}
export default CreditCard

View File

@ -0,0 +1,20 @@
const MapPin = ({ ...props }) => {
return (
<svg
viewBox="0 0 24 24"
width="24"
height="24"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
shapeRendering="geometricPrecision"
>
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z" />
<circle cx="12" cy="10" r="3" />
</svg>
)
}
export default MapPin

View File

@ -14,3 +14,5 @@ export { default as RightArrow } from './RightArrow'
export { default as Info } from './Info' export { default as Info } from './Info'
export { default as ChevronUp } from './ChevronUp' export { default as ChevronUp } from './ChevronUp'
export { default as Vercel } from './Vercel' export { default as Vercel } from './Vercel'
export { default as MapPin } from './MapPin'
export { default as CreditCard } from './CreditCard'

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

@ -59,7 +59,12 @@ type Action =
value: string value: string
} }
type MODAL_VIEWS = 'SIGNUP_VIEW' | 'LOGIN_VIEW' | 'FORGOT_VIEW' type MODAL_VIEWS =
| 'SIGNUP_VIEW'
| 'LOGIN_VIEW'
| 'FORGOT_VIEW'
| 'NEW_SHIPPING_ADDRESS'
| 'NEW_PAYMENT_METHOD'
type ToastText = string type ToastText = string
export const UIContext = React.createContext<State | any>(initialState) export const UIContext = React.createContext<State | any>(initialState)

View File

@ -30,7 +30,8 @@ const WishlistButton: FC<Props> = ({
const itemInWishlist = data?.items?.find( const itemInWishlist = data?.items?.find(
// @ts-ignore Wishlist is not always enabled // @ts-ignore Wishlist is not always enabled
(item) => (item) =>
item.product_id === productId && (item.variant_id as any) === variant.id item.product_id === Number(productId) &&
(item.variant_id as any) === Number(variant.id)
) )
const handleWishlistChange = async (e: any) => { const handleWishlistChange = async (e: any) => {

View File

@ -1,6 +1,11 @@
import type { ItemBody as WishlistItemBody } from '../wishlist' import type { ItemBody as WishlistItemBody } from '../wishlist'
import type { CartItemBody, OptionSelections } from '../../types' import type { CartItemBody, OptionSelections } from '../../types'
type BCWishlistItemBody = {
product_id: number
variant_id: number
}
type BCCartItemBody = { type BCCartItemBody = {
product_id: number product_id: number
variant_id: number variant_id: number
@ -8,9 +13,11 @@ type BCCartItemBody = {
option_selections?: OptionSelections option_selections?: OptionSelections
} }
export const parseWishlistItem = (item: WishlistItemBody) => ({ export const parseWishlistItem = (
product_id: item.productId, item: WishlistItemBody
variant_id: item.variantId, ): BCWishlistItemBody => ({
product_id: Number(item.productId),
variant_id: Number(item.variantId),
}) })
export const parseCartItem = (item: CartItemBody): BCCartItemBody => ({ export const parseCartItem = (item: CartItemBody): BCCartItemBody => ({

View File

@ -68,14 +68,15 @@ async function getCustomerWishlist({
const productsById = graphqlData.products.reduce<{ const productsById = graphqlData.products.reduce<{
[k: number]: ProductEdge [k: number]: ProductEdge
}>((prods, p) => { }>((prods, p) => {
prods[Number(p.node.entityId)] = p as any prods[Number(p.id)] = p as any
return prods return prods
}, {}) }, {})
// Populate the wishlist items with the graphql products // Populate the wishlist items with the graphql products
wishlist.items.forEach((item) => { wishlist.items.forEach((item) => {
const product = item && productsById[item.product_id!] const product = item && productsById[item.product_id!]
if (item && product) { if (item && product) {
item.product = product.node // @ts-ignore Fix this type when the wishlist type is properly defined
item.product = product
} }
}) })
} }

View File

@ -18,7 +18,7 @@ export const handler: SWRHook<
url: '/api/bigcommerce/wishlist', url: '/api/bigcommerce/wishlist',
method: 'GET', method: 'GET',
}, },
fetcher({ input: { customerId, includeProducts }, options, fetch }) { async fetcher({ input: { customerId, includeProducts }, options, fetch }) {
if (!customerId) return null if (!customerId) return null
// Use a dummy base as we only care about the relative path // Use a dummy base as we only care about the relative path
@ -35,7 +35,7 @@ export const handler: SWRHook<
const { data: customer } = useCustomer() const { data: customer } = useCustomer()
const response = useData({ const response = useData({
input: [ input: [
['customerId', (customer as any)?.id], ['customerId', customer?.entityId],
['includeProducts', input?.includeProducts], ['includeProducts', input?.includeProducts],
], ],
swrOptions: { swrOptions: {

View File

@ -5,10 +5,10 @@ import {
API_TOKEN, API_TOKEN,
SHOPIFY_CHECKOUT_ID_COOKIE, SHOPIFY_CHECKOUT_ID_COOKIE,
SHOPIFY_CUSTOMER_TOKEN_COOKIE, SHOPIFY_CUSTOMER_TOKEN_COOKIE,
SHOPIFY_COOKIE_EXPIRE,
} from '../const' } from '../const'
if (!API_URL) { if (!API_URL) {
console.log(process.env)
throw new Error( throw new Error(
`The environment variable NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN is missing and it's required to access your store` `The environment variable NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN is missing and it's required to access your store`
) )
@ -44,10 +44,11 @@ export class Config {
} }
const config = new Config({ const config = new Config({
locale: 'en-US',
commerceUrl: API_URL, commerceUrl: API_URL,
apiToken: API_TOKEN!, apiToken: API_TOKEN!,
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE, cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
cartCookieMaxAge: 60 * 60 * 24 * 30, cartCookieMaxAge: SHOPIFY_COOKIE_EXPIRE,
fetch: fetchGraphqlApi, fetch: fetchGraphqlApi,
customerCookie: SHOPIFY_CUSTOMER_TOKEN_COOKIE, customerCookie: SHOPIFY_CUSTOMER_TOKEN_COOKIE,
}) })

View File

@ -1,6 +1,7 @@
import { import {
SHOPIFY_CHECKOUT_ID_COOKIE, SHOPIFY_CHECKOUT_ID_COOKIE,
SHOPIFY_CHECKOUT_URL_COOKIE, SHOPIFY_CHECKOUT_URL_COOKIE,
SHOPIFY_COOKIE_EXPIRE,
} from '../../const' } from '../../const'
import checkoutCreateMutation from '../../utils/mutations/checkout-create' import checkoutCreateMutation from '../../utils/mutations/checkout-create'
@ -15,8 +16,11 @@ export const checkoutCreate = async (fetch: any) => {
const checkoutId = checkout?.id const checkoutId = checkout?.id
if (checkoutId) { if (checkoutId) {
Cookies.set(SHOPIFY_CHECKOUT_ID_COOKIE, checkoutId) const options = {
Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout?.webUrl) expires: SHOPIFY_COOKIE_EXPIRE,
}
Cookies.set(SHOPIFY_CHECKOUT_ID_COOKIE, checkoutId, options)
Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout?.webUrl, options)
} }
return checkout return checkout

View File

@ -25,12 +25,13 @@ const getAllPages = async (options?: {
}): Promise<ReturnType> => { }): Promise<ReturnType> => {
let { config, variables = { first: 250 } } = options ?? {} let { config, variables = { first: 250 } } = options ?? {}
config = getConfig(config) config = getConfig(config)
const { locale } = config
const { data } = await config.fetch(getAllPagesQuery, { variables }) const { data } = await config.fetch(getAllPagesQuery, { variables })
const pages = data.pages?.edges?.map( const pages = data.pages?.edges?.map(
({ node: { title: name, handle, ...node } }: PageEdge) => ({ ({ node: { title: name, handle, ...node } }: PageEdge) => ({
...node, ...node,
url: `/${handle}`, url: `/${locale}/${handle}`,
name, name,
}) })
) )

View File

@ -3,33 +3,32 @@ import getPageQuery from '../utils/queries/get-page-query'
import { Page } from './get-all-pages' import { Page } from './get-all-pages'
type Variables = { type Variables = {
slug: string id: string
} }
type ReturnType = { export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
page: Page
}
const getPage = async (options: { const getPage = async (options: {
variables: Variables variables: Variables
config: ShopifyConfig config: ShopifyConfig
preview?: boolean preview?: boolean
}): Promise<ReturnType> => { }): Promise<GetPageResult> => {
let { config, variables } = options ?? {} let { config, variables } = options ?? {}
config = getConfig(config) config = getConfig(config)
const { locale } = config
const { data } = await config.fetch(getPageQuery, { const { data } = await config.fetch(getPageQuery, {
variables, variables,
}) })
const page = data.node
const { pageByHandle: page } = data
return { return {
page: page page: page
? { ? {
...page, ...page,
name: page.title, name: page.title,
url: page?.handle, url: `/${locale}/${page.handle}`,
} }
: null, : null,
} }

View File

@ -6,6 +6,8 @@ export const SHOPIFY_CUSTOMER_TOKEN_COOKIE = 'shopify_customerToken'
export const STORE_DOMAIN = process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN export const STORE_DOMAIN = process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN
export const SHOPIFY_COOKIE_EXPIRE = 30
export const API_URL = `https://${STORE_DOMAIN}/api/2021-01/graphql.json` export const API_URL = `https://${STORE_DOMAIN}/api/2021-01/graphql.json`
export const API_TOKEN = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN export const API_TOKEN = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN

View File

@ -4,6 +4,7 @@ import useSearch, { UseSearch } from '@commerce/product/use-search'
import { ProductEdge } from '../schema' import { ProductEdge } from '../schema'
import { import {
getAllProductsQuery, getAllProductsQuery,
getCollectionProductsQuery,
getSearchVariables, getSearchVariables,
normalizeProduct, normalizeProduct,
} from '../utils' } from '../utils'
@ -14,8 +15,8 @@ export default useSearch as UseSearch<typeof handler>
export type SearchProductsInput = { export type SearchProductsInput = {
search?: string search?: string
categoryId?: number categoryId?: string
brandId?: number brandId?: string
sort?: string sort?: string
} }
@ -23,6 +24,7 @@ export type SearchProductsData = {
products: Product[] products: Product[]
found: boolean found: boolean
} }
export const handler: SWRHook< export const handler: SWRHook<
SearchProductsData, SearchProductsData,
SearchProductsInput, SearchProductsInput,
@ -32,18 +34,30 @@ export const handler: SWRHook<
query: getAllProductsQuery, query: getAllProductsQuery,
}, },
async fetcher({ input, options, fetch }) { async fetcher({ input, options, fetch }) {
const resp = await fetch({ const { categoryId, brandId } = input
query: options?.query,
const data = await fetch({
query: categoryId ? getCollectionProductsQuery : options.query,
method: options?.method, method: options?.method,
variables: getSearchVariables(input), variables: getSearchVariables(input),
}) })
const edges = resp.products?.edges
let edges
if (categoryId) {
edges = data.node?.products?.edges ?? []
if (brandId) {
edges = edges.filter(
({ node: { vendor } }: ProductEdge) => vendor === brandId
)
}
} else {
edges = data.products?.edges ?? []
}
return { return {
products: edges?.map(({ node: p }: ProductEdge) => products: edges.map(({ node }: ProductEdge) => normalizeProduct(node)),
// TODO: Fix this product type found: !!edges.length,
normalizeProduct(p as any)
),
found: !!edges?.length,
} }
}, },
useHook: ({ useData }) => (input = {}) => { useHook: ({ useData }) => (input = {}) => {

View File

@ -1,12 +1,21 @@
import Cookies from 'js-cookie' import Cookies, { CookieAttributes } from 'js-cookie'
import { SHOPIFY_CUSTOMER_TOKEN_COOKIE } from '../const' import { SHOPIFY_COOKIE_EXPIRE, SHOPIFY_CUSTOMER_TOKEN_COOKIE } from '../const'
export const getCustomerToken = () => Cookies.get(SHOPIFY_CUSTOMER_TOKEN_COOKIE) export const getCustomerToken = () => Cookies.get(SHOPIFY_CUSTOMER_TOKEN_COOKIE)
export const setCustomerToken = (token: string | null, options?: any) => { export const setCustomerToken = (
token: string | null,
options?: CookieAttributes
) => {
if (!token) { if (!token) {
Cookies.remove(SHOPIFY_CUSTOMER_TOKEN_COOKIE) Cookies.remove(SHOPIFY_CUSTOMER_TOKEN_COOKIE)
} else { } else {
Cookies.set(SHOPIFY_CUSTOMER_TOKEN_COOKIE, token, options) Cookies.set(
SHOPIFY_CUSTOMER_TOKEN_COOKIE,
token,
options ?? {
expires: SHOPIFY_COOKIE_EXPIRE,
}
)
} }
} }

View File

@ -17,8 +17,8 @@ const getCategories = async (config: ShopifyConfig): Promise<Category[]> => {
return ( return (
data.collections?.edges?.map( data.collections?.edges?.map(
({ node: { title: name, handle } }: CollectionEdge) => ({ ({ node: { id: entityId, title: name, handle } }: CollectionEdge) => ({
entityId: handle, entityId,
name, name,
path: `/${handle}`, path: `/${handle}`,
}) })

View File

@ -2,9 +2,9 @@ import getSortVariables from './get-sort-variables'
import type { SearchProductsInput } from '../product/use-search' import type { SearchProductsInput } from '../product/use-search'
export const getSearchVariables = ({ export const getSearchVariables = ({
categoryId,
brandId, brandId,
search, search,
categoryId,
sort, sort,
}: SearchProductsInput) => { }: SearchProductsInput) => {
let query = '' let query = ''
@ -13,17 +13,14 @@ export const getSearchVariables = ({
query += `product_type:${search} OR title:${search} OR tag:${search}` query += `product_type:${search} OR title:${search} OR tag:${search}`
} }
if (categoryId) {
query += `tag:${categoryId}`
}
if (brandId) { if (brandId) {
query += `${categoryId ? ' AND ' : ''}vendor:${brandId}` query += `${search ? ' AND ' : ''}vendor:${brandId}`
} }
return { return {
categoryId,
query, query,
...getSortVariables(sort), ...getSortVariables(sort, !!categoryId),
} }
} }

View File

@ -1,4 +1,4 @@
const getSortVariables = (sort?: string) => { const getSortVariables = (sort?: string, isCategory = false) => {
let output = {} let output = {}
switch (sort) { switch (sort) {
case 'price-asc': case 'price-asc':
@ -21,7 +21,7 @@ const getSortVariables = (sort?: string) => {
break break
case 'latest-desc': case 'latest-desc':
output = { output = {
sortKey: 'CREATED_AT', sortKey: isCategory ? 'CREATED' : 'CREATED_AT',
reverse: true, reverse: true,
} }
break break

View File

@ -1,3 +1,5 @@
import { Product } from '@commerce/types'
import { import {
Product as ShopifyProduct, Product as ShopifyProduct,
Checkout, Checkout,
@ -5,8 +7,8 @@ import {
SelectedOption, SelectedOption,
ImageConnection, ImageConnection,
ProductVariantConnection, ProductVariantConnection,
ProductOption,
MoneyV2, MoneyV2,
ProductOption,
} from '../schema' } from '../schema'
import type { Cart, LineItem } from '../types' import type { Cart, LineItem } from '../types'
@ -19,18 +21,26 @@ const money = ({ amount, currencyCode }: MoneyV2) => {
} }
const normalizeProductOption = ({ const normalizeProductOption = ({
id,
name: displayName, name: displayName,
values, values,
...rest
}: ProductOption) => { }: ProductOption) => {
return { return {
__typename: 'MultipleChoiceOption', __typename: 'MultipleChoiceOption',
id,
displayName, displayName,
values: values.map((value) => ({ values: values.map((value) => {
let output: any = {
label: value, label: value,
hexColors: displayName === 'Color' ? [value] : null, }
})), if (displayName === 'Color') {
...rest, output = {
...output,
hexColors: [value],
}
}
return output
}),
} }
} }
@ -41,8 +51,16 @@ const normalizeProductImages = ({ edges }: ImageConnection) =>
})) }))
const normalizeProductVariants = ({ edges }: ProductVariantConnection) => { const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
return edges?.map(({ node: { id, selectedOptions } }) => ({ return edges?.map(
({
node: { id, selectedOptions, sku, title, priceV2, compareAtPriceV2 },
}) => ({
id, id,
name: title,
sku: sku ?? id,
price: +priceV2.amount,
listPrice: +compareAtPriceV2?.amount,
requiresShipping: true,
options: selectedOptions.map(({ name, value }: SelectedOption) => options: selectedOptions.map(({ name, value }: SelectedOption) =>
normalizeProductOption({ normalizeProductOption({
id, id,
@ -50,10 +68,11 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
values: [value], values: [value],
}) })
), ),
})) })
)
} }
export function normalizeProduct(productNode: ShopifyProduct): any { export function normalizeProduct(productNode: ShopifyProduct): Product {
const { const {
id, id,
title: name, title: name,
@ -95,8 +114,8 @@ export function normalizeCart(checkout: Checkout): Cart {
}, },
taxesIncluded: checkout.taxesIncluded, taxesIncluded: checkout.taxesIncluded,
lineItems: checkout.lineItems?.edges.map(normalizeLineItem), lineItems: checkout.lineItems?.edges.map(normalizeLineItem),
lineItemsSubtotalPrice: checkout.subtotalPriceV2?.amount, lineItemsSubtotalPrice: +checkout.subtotalPriceV2?.amount,
subtotalPrice: checkout.subtotalPriceV2?.amount, subtotalPrice: +checkout.subtotalPriceV2?.amount,
totalPrice: checkout.totalPriceV2?.amount, totalPrice: checkout.totalPriceV2?.amount,
discounts: [], discounts: [],
} }

View File

@ -1,10 +1,4 @@
export const productsFragment = ` export const productConnection = `
products(
first: $first
sortKey: $sortKey
reverse: $reverse
query: $query
) {
pageInfo { pageInfo {
hasNextPage hasNextPage
hasPreviousPage hasPreviousPage
@ -37,7 +31,16 @@ products(
} }
} }
} }
} }`
export const productsFragment = `
products(
first: $first
sortKey: $sortKey
reverse: $reverse
query: $query
) {
${productConnection}
} }
` `

View File

@ -1,16 +1,23 @@
import { productsFragment } from './get-all-products-query' import { productConnection } from './get-all-products-query'
const getCollectionProductsQuery = /* GraphQL */ ` const getCollectionProductsQuery = /* GraphQL */ `
query getProductsFromCollection( query getProductsFromCollection(
$categoryHandle: String! $categoryId: ID!
$first: Int = 250 $first: Int = 250
$query: String = "" $sortKey: ProductCollectionSortKeys = RELEVANCE
$sortKey: ProductSortKeys = RELEVANCE
$reverse: Boolean = false $reverse: Boolean = false
) { ) {
collectionByHandle(handle: $categoryHandle) node(id: $categoryId) {
{ id
${productsFragment} ... on Collection {
products(
first: $first
sortKey: $sortKey
reverse: $reverse
) {
${productConnection}
}
}
} }
} }
` `

View File

@ -1,12 +1,13 @@
export const getPageQuery = /* GraphQL */ ` export const getPageQuery = /* GraphQL */ `
query getPageBySlug($slug: String!) { query($id: ID!) {
pageByHandle(handle: $slug) { node(id: $id) {
id id
... on Page {
title title
handle handle
body body
bodySummary bodySummary
url }
} }
} }
` `

View File

@ -32,6 +32,7 @@ const getProductQuery = /* GraphQL */ `
node { node {
id id
title title
sku
selectedOptions { selectedOptions {
name name
value value

View File

@ -1,68 +0,0 @@
// TODO: Fix the types in this file
// import { Product, Image } from '../types'
type Product = any
type Image = any
export default function toCommerceProducts(products: Product[]) {
return products.map((product: Product) => {
return {
id: product.id,
entityId: product.id,
name: product.title,
slug: product.handle,
title: product.title,
vendor: product.vendor,
description: product.descriptionHtml,
path: `/${product.handle}`,
price: {
value: +product.variants[0].price,
currencyCode: 'USD', // TODO
},
images: product.images.map((image: Image) => {
return {
url: image.src,
}
}),
// TODO: Fix the variant type
variants: product.variants.map((variant: any) => {
return {
id: variant.id,
// TODO: Fix the selectedOption type
options: variant.selectedOptions.map((selectedOption: any) => {
return {
__typename: 'MultipleChoiceOption',
displayName: selectedOption.name,
values: [
{
node: {
id: variant.id,
label: selectedOption.value,
},
},
],
}
}),
}
}),
// TODO: Fix the option type
productOptions: product.options.map((option: any) => {
return {
__typename: 'MultipleChoiceOption',
displayName: option.name,
// TODO: Fix the value type
values: option.values.map((value: any) => {
return {
node: {
entityId: 1,
label: value.value,
hexColors: [value.value],
},
}
}),
}
}),
options: [],
}
})
}

View File

@ -2,9 +2,7 @@
// Shopify doesn't have a wishlist // Shopify doesn't have a wishlist
import { HookFetcher } from '@commerce/utils/types' import { HookFetcher } from '@commerce/utils/types'
import useCommerceWishlist from '@commerce/wishlist/use-wishlist'
import { Product } from '../schema' import { Product } from '../schema'
import useCustomer from '../customer/use-customer'
const defaultOpts = {} const defaultOpts = {}

View File

@ -42,9 +42,160 @@ 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 {
// 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

@ -8,22 +8,18 @@
"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": "next-unused",
"commerce": "node scripts/commerce.js",
"generate": "graphql-codegen", "generate": "graphql-codegen",
"generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js" "generate:definitions": "node framework/bigcommerce/scripts/generate-definitions.js"
}, },
"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",
@ -39,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",
@ -48,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",
@ -74,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

@ -5,7 +5,7 @@ import useCart from '@framework/cart/use-cart'
import usePrice from '@framework/product/use-price' import usePrice from '@framework/product/use-price'
import { Layout } from '@components/common' import { Layout } from '@components/common'
import { Button, Text } from '@components/ui' import { Button, Text } from '@components/ui'
import { Bag, Cross, Check } from '@components/icons' import { Bag, Cross, Check, MapPin, CreditCard } from '@components/icons'
import { CartItem } from '@components/cart' import { CartItem } from '@components/cart'
export async function getStaticProps({ export async function getStaticProps({
@ -38,7 +38,7 @@ export default function Cart() {
) )
return ( return (
<div className="grid lg:grid-cols-12"> <div className="grid lg:grid-cols-12 w-full max-w-7xl mx-auto">
<div className="lg:col-span-8"> <div className="lg:col-span-8">
{isLoading || isEmpty ? ( {isLoading || isEmpty ? (
<div className="flex-1 px-12 py-24 flex flex-col justify-center items-center "> <div className="flex-1 px-12 py-24 flex flex-col justify-center items-center ">
@ -103,6 +103,35 @@ export default function Cart() {
</div> </div>
<div className="lg:col-span-4"> <div className="lg:col-span-4">
<div className="flex-shrink-0 px-4 py-24 sm:px-6"> <div className="flex-shrink-0 px-4 py-24 sm:px-6">
{process.env.COMMERCE_CUSTOMCHECKOUT_ENABLED && (
<>
{/* Shipping Address */}
{/* Only available with customCheckout set to true - Meaning that the provider does offer checkout functionality. */}
<div className="rounded-md border border-accents-2 px-6 py-6 mb-4 text-center flex items-center justify-center cursor-pointer hover:border-accents-4">
<div className="mr-5">
<MapPin />
</div>
<div className="text-sm text-center font-medium">
<span className="uppercase">+ Add Shipping Address</span>
{/* <span>
1046 Kearny Street.<br/>
San Franssisco, California
</span> */}
</div>
</div>
{/* Payment Method */}
{/* Only available with customCheckout set to true - Meaning that the provider does offer checkout functionality. */}
<div className="rounded-md border border-accents-2 px-6 py-6 mb-4 text-center flex items-center justify-center cursor-pointer hover:border-accents-4">
<div className="mr-5">
<CreditCard />
</div>
<div className="text-sm text-center font-medium">
<span className="uppercase">+ Add Payment Method</span>
{/* <span>VISA #### #### #### 2345</span> */}
</div>
</div>
</>
)}
<div className="border-t border-accents-2"> <div className="border-t border-accents-2">
<ul className="py-3"> <ul className="py-3">
<li className="flex justify-between py-1"> <li className="flex justify-between py-1">

View File

@ -1,7 +1,4 @@
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import type { GetStaticPropsContext } from 'next' import type { GetStaticPropsContext } from 'next'
import { Heart } from '@components/icons' import { Heart } from '@components/icons'
import { Layout } from '@components/common' import { Layout } from '@components/common'
import { Text, Container } from '@components/ui' import { Text, Container } from '@components/ui'
@ -36,8 +33,7 @@ export async function getStaticProps({
export default function Wishlist() { export default function Wishlist() {
const { data: customer } = useCustomer() const { data: customer } = useCustomer()
// @ts-ignore Shopify - Fix this types // @ts-ignore Shopify - Fix this types
const { data, isLoading, isEmpty } = useWishlist() const { data, isLoading, isEmpty } = useWishlist({ includeProducts: true })
const router = useRouter()
return ( return (
<Container> <Container>
@ -60,7 +56,7 @@ export default function Wishlist() {
data && data &&
// @ts-ignore Shopify - Fix this types // @ts-ignore Shopify - Fix this types
data.items?.map((item) => ( data.items?.map((item) => (
<WishlistCard key={item.id} product={item as any} /> <WishlistCard key={item.id} product={item.product! as any} />
)) ))
)} )}
</div> </div>

View File

@ -1 +0,0 @@
console.log('Hello')

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==