mirror of
https://github.com/vercel/commerce.git
synced 2025-06-18 13:11:23 +00:00
Merge branch 'agnostic' of github.com:vercel/commerce into bigcommerce
This commit is contained in:
commit
eb4a13f173
@ -1,3 +1,7 @@
|
||||
{
|
||||
"provider": "bigcommerce"
|
||||
"provider": "bigcommerce",
|
||||
"features": {
|
||||
"wishlist": true,
|
||||
"customCheckout": false
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { FC } from 'react'
|
||||
import cn from 'classnames'
|
||||
import { UserNav } from '@components/common'
|
||||
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 Link from 'next/link'
|
||||
import CartItem from '../CartItem'
|
||||
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 { closeSidebar } = useUI()
|
||||
@ -88,9 +88,14 @@ const CartSidebarView: FC = () => {
|
||||
) : (
|
||||
<>
|
||||
<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">
|
||||
My Cart
|
||||
</h2>
|
||||
<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
|
||||
</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">
|
||||
{data!.lineItems.map((item: any) => (
|
||||
<CartItem
|
||||
|
20
components/icons/CreditCard.tsx
Normal file
20
components/icons/CreditCard.tsx
Normal 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
|
20
components/icons/MapPin.tsx
Normal file
20
components/icons/MapPin.tsx
Normal 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
|
@ -14,3 +14,5 @@ export { default as RightArrow } from './RightArrow'
|
||||
export { default as Info } from './Info'
|
||||
export { default as ChevronUp } from './ChevronUp'
|
||||
export { default as Vercel } from './Vercel'
|
||||
export { default as MapPin } from './MapPin'
|
||||
export { default as CreditCard } from './CreditCard'
|
||||
|
@ -59,7 +59,12 @@ type Action =
|
||||
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
|
||||
|
||||
export const UIContext = React.createContext<State | any>(initialState)
|
||||
|
@ -26,9 +26,12 @@ const WishlistButton: FC<Props> = ({
|
||||
const { openModal, setModalView } = useUI()
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// @ts-ignore Wishlist is not always enabled
|
||||
const itemInWishlist = data?.items?.find(
|
||||
// @ts-ignore Wishlist is not always enabled
|
||||
(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) => {
|
||||
|
@ -22,6 +22,7 @@ const WishlistCard: FC<Props> = ({ product }) => {
|
||||
baseAmount: product.prices?.retailPrice?.value,
|
||||
currencyCode: product.prices?.price?.currencyCode!,
|
||||
})
|
||||
// @ts-ignore Wishlist is not always enabled
|
||||
const removeItem = useRemoveItem({ wishlist: { includeProducts: true } })
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [removing, setRemoving] = useState(false)
|
||||
|
@ -3,4 +3,4 @@ BIGCOMMERCE_STOREFRONT_API_TOKEN=
|
||||
BIGCOMMERCE_STORE_API_URL=
|
||||
BIGCOMMERCE_STORE_API_TOKEN=
|
||||
BIGCOMMERCE_STORE_API_CLIENT_ID=
|
||||
BIGCOMMERCE_CHANNEL_ID=
|
||||
BIGCOMMERCE_CHANNEL_ID=
|
||||
|
@ -1,6 +1,11 @@
|
||||
import type { ItemBody as WishlistItemBody } from '../wishlist'
|
||||
import type { CartItemBody, OptionSelections } from '../../types'
|
||||
|
||||
type BCWishlistItemBody = {
|
||||
product_id: number
|
||||
variant_id: number
|
||||
}
|
||||
|
||||
type BCCartItemBody = {
|
||||
product_id: number
|
||||
variant_id: number
|
||||
@ -8,9 +13,11 @@ type BCCartItemBody = {
|
||||
option_selections?: OptionSelections
|
||||
}
|
||||
|
||||
export const parseWishlistItem = (item: WishlistItemBody) => ({
|
||||
product_id: item.productId,
|
||||
variant_id: item.variantId,
|
||||
export const parseWishlistItem = (
|
||||
item: WishlistItemBody
|
||||
): BCWishlistItemBody => ({
|
||||
product_id: Number(item.productId),
|
||||
variant_id: Number(item.variantId),
|
||||
})
|
||||
|
||||
export const parseCartItem = (item: CartItemBody): BCCartItemBody => ({
|
||||
|
@ -68,14 +68,15 @@ async function getCustomerWishlist({
|
||||
const productsById = graphqlData.products.reduce<{
|
||||
[k: number]: ProductEdge
|
||||
}>((prods, p) => {
|
||||
prods[Number(p.node.entityId)] = p as any
|
||||
prods[Number(p.id)] = p as any
|
||||
return prods
|
||||
}, {})
|
||||
// Populate the wishlist items with the graphql products
|
||||
wishlist.items.forEach((item) => {
|
||||
const product = item && productsById[item.product_id!]
|
||||
if (item && product) {
|
||||
item.product = product.node
|
||||
// @ts-ignore Fix this type when the wishlist type is properly defined
|
||||
item.product = product
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const handler: SWRHook<
|
||||
url: '/api/bigcommerce/wishlist',
|
||||
method: 'GET',
|
||||
},
|
||||
fetcher({ input: { customerId, includeProducts }, options, fetch }) {
|
||||
async fetcher({ input: { customerId, includeProducts }, options, fetch }) {
|
||||
if (!customerId) return null
|
||||
|
||||
// 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 response = useData({
|
||||
input: [
|
||||
['customerId', (customer as any)?.id],
|
||||
['customerId', customer?.entityId],
|
||||
['includeProducts', input?.includeProducts],
|
||||
],
|
||||
swrOptions: {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { Wishlist as BCWishlist } from '@framework/api/wishlist'
|
||||
import type { Customer as BCCustomer } from '@framework/api/customers'
|
||||
import type { SearchProductsData as BCSearchProductsData } from '@framework/api/catalog/products'
|
||||
import type { Wishlist as BCWishlist } from '../bigcommerce/api/wishlist'
|
||||
import type { Customer as BCCustomer } from '../bigcommerce/api/customers'
|
||||
import type { SearchProductsData as BCSearchProductsData } from '../bigcommerce/api/catalog/products'
|
||||
|
||||
export type Discount = {
|
||||
// The value of the discount, can be an amount or percentage
|
||||
|
@ -5,10 +5,10 @@ import {
|
||||
API_TOKEN,
|
||||
SHOPIFY_CHECKOUT_ID_COOKIE,
|
||||
SHOPIFY_CUSTOMER_TOKEN_COOKIE,
|
||||
SHOPIFY_COOKIE_EXPIRE,
|
||||
} from '../const'
|
||||
|
||||
if (!API_URL) {
|
||||
console.log(process.env)
|
||||
throw new Error(
|
||||
`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({
|
||||
locale: 'en-US',
|
||||
commerceUrl: API_URL,
|
||||
apiToken: API_TOKEN!,
|
||||
cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE,
|
||||
cartCookieMaxAge: 60 * 60 * 24 * 30,
|
||||
cartCookieMaxAge: SHOPIFY_COOKIE_EXPIRE,
|
||||
fetch: fetchGraphqlApi,
|
||||
customerCookie: SHOPIFY_CUSTOMER_TOKEN_COOKIE,
|
||||
})
|
||||
|
@ -4,7 +4,7 @@ import useCommerceCart, {
|
||||
UseCart,
|
||||
} from '@commerce/cart/use-cart'
|
||||
|
||||
import { Cart } from '@commerce/types'
|
||||
import { Cart } from '../types'
|
||||
import { SWRHook } from '@commerce/utils/types'
|
||||
import { checkoutCreate, checkoutToCart } from './utils'
|
||||
import getCheckoutQuery from '../utils/queries/get-checkout-query'
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
SHOPIFY_CHECKOUT_ID_COOKIE,
|
||||
SHOPIFY_CHECKOUT_URL_COOKIE,
|
||||
SHOPIFY_COOKIE_EXPIRE,
|
||||
} from '../../const'
|
||||
|
||||
import checkoutCreateMutation from '../../utils/mutations/checkout-create'
|
||||
@ -15,8 +16,11 @@ export const checkoutCreate = async (fetch: any) => {
|
||||
const checkoutId = checkout?.id
|
||||
|
||||
if (checkoutId) {
|
||||
Cookies.set(SHOPIFY_CHECKOUT_ID_COOKIE, checkoutId)
|
||||
Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout?.webUrl)
|
||||
const options = {
|
||||
expires: SHOPIFY_COOKIE_EXPIRE,
|
||||
}
|
||||
Cookies.set(SHOPIFY_CHECKOUT_ID_COOKIE, checkoutId, options)
|
||||
Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout?.webUrl, options)
|
||||
}
|
||||
|
||||
return checkout
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Cart } from '@commerce/types'
|
||||
import { Cart } from '../../types'
|
||||
import { CommerceError, ValidationError } from '@commerce/utils/errors'
|
||||
|
||||
import {
|
||||
|
@ -25,12 +25,13 @@ const getAllPages = async (options?: {
|
||||
}): Promise<ReturnType> => {
|
||||
let { config, variables = { first: 250 } } = options ?? {}
|
||||
config = getConfig(config)
|
||||
const { locale } = config
|
||||
const { data } = await config.fetch(getAllPagesQuery, { variables })
|
||||
|
||||
const pages = data.pages?.edges?.map(
|
||||
({ node: { title: name, handle, ...node } }: PageEdge) => ({
|
||||
...node,
|
||||
url: `/${handle}`,
|
||||
url: `/${locale}/${handle}`,
|
||||
name,
|
||||
})
|
||||
)
|
||||
|
@ -3,33 +3,32 @@ import getPageQuery from '../utils/queries/get-page-query'
|
||||
import { Page } from './get-all-pages'
|
||||
|
||||
type Variables = {
|
||||
slug: string
|
||||
id: string
|
||||
}
|
||||
|
||||
type ReturnType = {
|
||||
page: Page
|
||||
}
|
||||
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
|
||||
|
||||
const getPage = async (options: {
|
||||
variables: Variables
|
||||
config: ShopifyConfig
|
||||
preview?: boolean
|
||||
}): Promise<ReturnType> => {
|
||||
}): Promise<GetPageResult> => {
|
||||
let { config, variables } = options ?? {}
|
||||
|
||||
config = getConfig(config)
|
||||
const { locale } = config
|
||||
|
||||
const { data } = await config.fetch(getPageQuery, {
|
||||
variables,
|
||||
})
|
||||
|
||||
const { pageByHandle: page } = data
|
||||
const page = data.node
|
||||
|
||||
return {
|
||||
page: page
|
||||
? {
|
||||
...page,
|
||||
name: page.title,
|
||||
url: page?.handle,
|
||||
url: `/${locale}/${page.handle}`,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
|
@ -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 SHOPIFY_COOKIE_EXPIRE = 30
|
||||
|
||||
export const API_URL = `https://${STORE_DOMAIN}/api/2021-01/graphql.json`
|
||||
|
||||
export const API_TOKEN = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN
|
||||
|
@ -28,7 +28,8 @@ export type ShopifyProps = {
|
||||
export function CommerceProvider({ children, ...config }: ShopifyProps) {
|
||||
return (
|
||||
<CoreCommerceProvider
|
||||
provider={shopifyProvider}
|
||||
// TODO: Fix this type
|
||||
provider={shopifyProvider as any}
|
||||
config={{ ...shopifyConfig, ...config }}
|
||||
>
|
||||
{children}
|
||||
|
@ -4,6 +4,7 @@ import useSearch, { UseSearch } from '@commerce/product/use-search'
|
||||
import { ProductEdge } from '../schema'
|
||||
import {
|
||||
getAllProductsQuery,
|
||||
getCollectionProductsQuery,
|
||||
getSearchVariables,
|
||||
normalizeProduct,
|
||||
} from '../utils'
|
||||
@ -14,8 +15,8 @@ export default useSearch as UseSearch<typeof handler>
|
||||
|
||||
export type SearchProductsInput = {
|
||||
search?: string
|
||||
categoryId?: number
|
||||
brandId?: number
|
||||
categoryId?: string
|
||||
brandId?: string
|
||||
sort?: string
|
||||
}
|
||||
|
||||
@ -23,6 +24,7 @@ export type SearchProductsData = {
|
||||
products: Product[]
|
||||
found: boolean
|
||||
}
|
||||
|
||||
export const handler: SWRHook<
|
||||
SearchProductsData,
|
||||
SearchProductsInput,
|
||||
@ -32,18 +34,30 @@ export const handler: SWRHook<
|
||||
query: getAllProductsQuery,
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {
|
||||
const resp = await fetch({
|
||||
query: options?.query,
|
||||
const { categoryId, brandId } = input
|
||||
|
||||
const data = await fetch({
|
||||
query: categoryId ? getCollectionProductsQuery : options.query,
|
||||
method: options?.method,
|
||||
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 {
|
||||
products: edges?.map(({ node: p }: ProductEdge) =>
|
||||
// TODO: Fix this product type
|
||||
normalizeProduct(p as any)
|
||||
),
|
||||
found: !!edges?.length,
|
||||
products: edges.map(({ node }: ProductEdge) => normalizeProduct(node)),
|
||||
found: !!edges.length,
|
||||
}
|
||||
},
|
||||
useHook: ({ useData }) => (input = {}) => {
|
||||
|
@ -1,12 +1,21 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { SHOPIFY_CUSTOMER_TOKEN_COOKIE } from '../const'
|
||||
import Cookies, { CookieAttributes } from 'js-cookie'
|
||||
import { SHOPIFY_COOKIE_EXPIRE, SHOPIFY_CUSTOMER_TOKEN_COOKIE } from '../const'
|
||||
|
||||
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) {
|
||||
Cookies.remove(SHOPIFY_CUSTOMER_TOKEN_COOKIE)
|
||||
} else {
|
||||
Cookies.set(SHOPIFY_CUSTOMER_TOKEN_COOKIE, token, options)
|
||||
Cookies.set(
|
||||
SHOPIFY_CUSTOMER_TOKEN_COOKIE,
|
||||
token,
|
||||
options ?? {
|
||||
expires: SHOPIFY_COOKIE_EXPIRE,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { CollectionEdge } from '../schema'
|
||||
import getSiteCollectionsQuery from './queries/get-all-collections-query'
|
||||
|
||||
export type Category = {
|
||||
endityId: string
|
||||
entityId: string
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
@ -17,8 +17,8 @@ const getCategories = async (config: ShopifyConfig): Promise<Category[]> => {
|
||||
|
||||
return (
|
||||
data.collections?.edges?.map(
|
||||
({ node: { title: name, handle } }: CollectionEdge) => ({
|
||||
entityId: handle,
|
||||
({ node: { id: entityId, title: name, handle } }: CollectionEdge) => ({
|
||||
entityId,
|
||||
name,
|
||||
path: `/${handle}`,
|
||||
})
|
||||
|
@ -2,9 +2,9 @@ import getSortVariables from './get-sort-variables'
|
||||
import type { SearchProductsInput } from '../product/use-search'
|
||||
|
||||
export const getSearchVariables = ({
|
||||
categoryId,
|
||||
brandId,
|
||||
search,
|
||||
categoryId,
|
||||
sort,
|
||||
}: SearchProductsInput) => {
|
||||
let query = ''
|
||||
@ -13,17 +13,14 @@ export const getSearchVariables = ({
|
||||
query += `product_type:${search} OR title:${search} OR tag:${search}`
|
||||
}
|
||||
|
||||
if (categoryId) {
|
||||
query += `tag:${categoryId}`
|
||||
}
|
||||
|
||||
if (brandId) {
|
||||
query += `${categoryId ? ' AND ' : ''}vendor:${brandId}`
|
||||
query += `${search ? ' AND ' : ''}vendor:${brandId}`
|
||||
}
|
||||
|
||||
return {
|
||||
categoryId,
|
||||
query,
|
||||
...getSortVariables(sort),
|
||||
...getSortVariables(sort, !!categoryId),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const getSortVariables = (sort?: string) => {
|
||||
const getSortVariables = (sort?: string, isCategory = false) => {
|
||||
let output = {}
|
||||
switch (sort) {
|
||||
case 'price-asc':
|
||||
@ -21,7 +21,7 @@ const getSortVariables = (sort?: string) => {
|
||||
break
|
||||
case 'latest-desc':
|
||||
output = {
|
||||
sortKey: 'CREATED_AT',
|
||||
sortKey: isCategory ? 'CREATED' : 'CREATED_AT',
|
||||
reverse: true,
|
||||
}
|
||||
break
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Product } from '@commerce/types'
|
||||
|
||||
import {
|
||||
Product as ShopifyProduct,
|
||||
Checkout,
|
||||
@ -5,8 +7,8 @@ import {
|
||||
SelectedOption,
|
||||
ImageConnection,
|
||||
ProductVariantConnection,
|
||||
ProductOption,
|
||||
MoneyV2,
|
||||
ProductOption,
|
||||
} from '../schema'
|
||||
|
||||
import type { Cart, LineItem } from '../types'
|
||||
@ -19,18 +21,26 @@ const money = ({ amount, currencyCode }: MoneyV2) => {
|
||||
}
|
||||
|
||||
const normalizeProductOption = ({
|
||||
id,
|
||||
name: displayName,
|
||||
values,
|
||||
...rest
|
||||
}: ProductOption) => {
|
||||
return {
|
||||
__typename: 'MultipleChoiceOption',
|
||||
id,
|
||||
displayName,
|
||||
values: values.map((value) => ({
|
||||
label: value,
|
||||
hexColors: displayName === 'Color' ? [value] : null,
|
||||
})),
|
||||
...rest,
|
||||
values: values.map((value) => {
|
||||
let output: any = {
|
||||
label: value,
|
||||
}
|
||||
if (displayName === 'Color') {
|
||||
output = {
|
||||
...output,
|
||||
hexColors: [value],
|
||||
}
|
||||
}
|
||||
return output
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,19 +51,28 @@ const normalizeProductImages = ({ edges }: ImageConnection) =>
|
||||
}))
|
||||
|
||||
const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
|
||||
return edges?.map(({ node: { id, selectedOptions } }) => ({
|
||||
id,
|
||||
options: selectedOptions.map(({ name, value }: SelectedOption) =>
|
||||
normalizeProductOption({
|
||||
id,
|
||||
name,
|
||||
values: [value],
|
||||
})
|
||||
),
|
||||
}))
|
||||
return edges?.map(
|
||||
({
|
||||
node: { id, selectedOptions, sku, title, priceV2, compareAtPriceV2 },
|
||||
}) => ({
|
||||
id,
|
||||
name: title,
|
||||
sku: sku ?? id,
|
||||
price: +priceV2.amount,
|
||||
listPrice: +compareAtPriceV2?.amount,
|
||||
requiresShipping: true,
|
||||
options: selectedOptions.map(({ name, value }: SelectedOption) =>
|
||||
normalizeProductOption({
|
||||
id,
|
||||
name,
|
||||
values: [value],
|
||||
})
|
||||
),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function normalizeProduct(productNode: ShopifyProduct): any {
|
||||
export function normalizeProduct(productNode: ShopifyProduct): Product {
|
||||
const {
|
||||
id,
|
||||
title: name,
|
||||
@ -95,8 +114,8 @@ export function normalizeCart(checkout: Checkout): Cart {
|
||||
},
|
||||
taxesIncluded: checkout.taxesIncluded,
|
||||
lineItems: checkout.lineItems?.edges.map(normalizeLineItem),
|
||||
lineItemsSubtotalPrice: checkout.subtotalPriceV2?.amount,
|
||||
subtotalPrice: checkout.subtotalPriceV2?.amount,
|
||||
lineItemsSubtotalPrice: +checkout.subtotalPriceV2?.amount,
|
||||
subtotalPrice: +checkout.subtotalPriceV2?.amount,
|
||||
totalPrice: checkout.totalPriceV2?.amount,
|
||||
discounts: [],
|
||||
}
|
||||
|
@ -1,3 +1,38 @@
|
||||
export const productConnection = `
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
vendor
|
||||
handle
|
||||
description
|
||||
priceRange {
|
||||
minVariantPrice {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
}
|
||||
images(first: 1) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
originalSrc
|
||||
altText
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
export const productsFragment = `
|
||||
products(
|
||||
first: $first
|
||||
@ -5,39 +40,7 @@ products(
|
||||
reverse: $reverse
|
||||
query: $query
|
||||
) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
vendor
|
||||
handle
|
||||
description
|
||||
priceRange {
|
||||
minVariantPrice {
|
||||
amount
|
||||
currencyCode
|
||||
}
|
||||
}
|
||||
images(first: 1) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
originalSrc
|
||||
altText
|
||||
width
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${productConnection}
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -1,16 +1,23 @@
|
||||
import { productsFragment } from './get-all-products-query'
|
||||
import { productConnection } from './get-all-products-query'
|
||||
|
||||
const getCollectionProductsQuery = /* GraphQL */ `
|
||||
query getProductsFromCollection(
|
||||
$categoryHandle: String!
|
||||
$categoryId: ID!
|
||||
$first: Int = 250
|
||||
$query: String = ""
|
||||
$sortKey: ProductSortKeys = RELEVANCE
|
||||
$sortKey: ProductCollectionSortKeys = RELEVANCE
|
||||
$reverse: Boolean = false
|
||||
) {
|
||||
collectionByHandle(handle: $categoryHandle)
|
||||
{
|
||||
${productsFragment}
|
||||
node(id: $categoryId) {
|
||||
id
|
||||
... on Collection {
|
||||
products(
|
||||
first: $first
|
||||
sortKey: $sortKey
|
||||
reverse: $reverse
|
||||
) {
|
||||
${productConnection}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@ -1,12 +1,13 @@
|
||||
export const getPageQuery = /* GraphQL */ `
|
||||
query getPageBySlug($slug: String!) {
|
||||
pageByHandle(handle: $slug) {
|
||||
query($id: ID!) {
|
||||
node(id: $id) {
|
||||
id
|
||||
title
|
||||
handle
|
||||
body
|
||||
bodySummary
|
||||
url
|
||||
... on Page {
|
||||
title
|
||||
handle
|
||||
body
|
||||
bodySummary
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@ -32,6 +32,7 @@ const getProductQuery = /* GraphQL */ `
|
||||
node {
|
||||
id
|
||||
title
|
||||
sku
|
||||
selectedOptions {
|
||||
name
|
||||
value
|
||||
|
@ -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: [],
|
||||
}
|
||||
})
|
||||
}
|
@ -2,9 +2,7 @@
|
||||
// Shopify doesn't have a wishlist
|
||||
|
||||
import { HookFetcher } from '@commerce/utils/types'
|
||||
import useCommerceWishlist from '@commerce/wishlist/use-wishlist'
|
||||
import { Product } from '../schema'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
|
||||
const defaultOpts = {}
|
||||
|
||||
|
@ -25,7 +25,8 @@ export async function getStaticProps({
|
||||
const pageItem = pages.find((p) => (p.url ? getSlug(p.url) === slug : false))
|
||||
const data =
|
||||
pageItem &&
|
||||
(await getPage({ variables: { id: pageItem.id! }, config, preview }))
|
||||
// TODO: Shopify - Fix this type
|
||||
(await getPage({ variables: { id: pageItem.id! } as any, config, preview }))
|
||||
const page = data?.page
|
||||
|
||||
if (!page) {
|
||||
|
@ -5,7 +5,7 @@ import useCart from '@framework/cart/use-cart'
|
||||
import usePrice from '@framework/product/use-price'
|
||||
import { Layout } from '@components/common'
|
||||
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'
|
||||
|
||||
export async function getStaticProps({
|
||||
@ -38,7 +38,7 @@ export default function Cart() {
|
||||
)
|
||||
|
||||
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">
|
||||
{isLoading || isEmpty ? (
|
||||
<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 className="lg:col-span-4">
|
||||
<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">
|
||||
<ul className="py-3">
|
||||
<li className="flex justify-between py-1">
|
||||
|
@ -75,8 +75,10 @@ export default function Search({
|
||||
|
||||
const { data } = useSearch({
|
||||
search: typeof q === 'string' ? q : '',
|
||||
categoryId: activeCategory?.entityId,
|
||||
brandId: activeBrand?.entityId,
|
||||
// TODO: Shopify - Fix this type
|
||||
categoryId: activeCategory?.entityId as any,
|
||||
// TODO: Shopify - Fix this type
|
||||
brandId: (activeBrand as any)?.entityId,
|
||||
sort: typeof sort === 'string' ? sort : '',
|
||||
})
|
||||
|
||||
@ -266,6 +268,7 @@ export default function Search({
|
||||
className={cn(
|
||||
'block text-sm leading-5 text-gray-700 hover:bg-gray-100 lg:hover:bg-transparent hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900',
|
||||
{
|
||||
// @ts-ignore Shopify - Fix this types
|
||||
underline: activeBrand?.entityId === node.entityId,
|
||||
}
|
||||
)}
|
||||
|
@ -1,7 +1,4 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import type { GetStaticPropsContext } from 'next'
|
||||
|
||||
import { Heart } from '@components/icons'
|
||||
import { Layout } from '@components/common'
|
||||
import { Text, Container } from '@components/ui'
|
||||
@ -35,8 +32,8 @@ export async function getStaticProps({
|
||||
|
||||
export default function Wishlist() {
|
||||
const { data: customer } = useCustomer()
|
||||
const { data, isLoading, isEmpty } = useWishlist()
|
||||
const router = useRouter()
|
||||
// @ts-ignore Shopify - Fix this types
|
||||
const { data, isLoading, isEmpty } = useWishlist({ includeProducts: true })
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@ -57,8 +54,9 @@ export default function Wishlist() {
|
||||
</div>
|
||||
) : (
|
||||
data &&
|
||||
// @ts-ignore Shopify - Fix this types
|
||||
data.items?.map((item) => (
|
||||
<WishlistCard key={item.id} product={item as any} />
|
||||
<WishlistCard key={item.id} product={item.product! as any} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user