mirror of
https://github.com/vercel/commerce.git
synced 2025-05-17 15:06:59 +00:00
Updates
This commit is contained in:
parent
79d320501d
commit
3bcf9d1d53
@ -1,3 +1,6 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
import type { CookieAttributes } from 'js-cookie'
|
||||
import type { FetcherOptions } from '@vercel/commerce/utils/types'
|
||||
|
||||
import type {
|
||||
@ -7,7 +10,6 @@ import type {
|
||||
CustomerAccessTokenCreateInput,
|
||||
} from '../../schema'
|
||||
|
||||
import { setCustomerToken } from './helpers'
|
||||
import { throwUserErrors } from './throw-user-errors'
|
||||
|
||||
import {
|
||||
@ -15,6 +17,27 @@ import {
|
||||
customerAccessTokenCreateMutation,
|
||||
} from './mutations'
|
||||
|
||||
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?: CookieAttributes
|
||||
) => {
|
||||
if (!token) {
|
||||
Cookies.remove(SHOPIFY_CUSTOMER_TOKEN_COOKIE)
|
||||
} else {
|
||||
Cookies.set(
|
||||
SHOPIFY_CUSTOMER_TOKEN_COOKIE,
|
||||
token,
|
||||
options ?? {
|
||||
expires: SHOPIFY_COOKIE_EXPIRE,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const handleLogin = (data: any) => {
|
||||
const response = data.customerAccessTokenCreate
|
||||
throwUserErrors(response?.customerUserErrors)
|
65
packages/shopify/src/utils/cart.ts
Normal file
65
packages/shopify/src/utils/cart.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import type { FetcherOptions } from '@vercel/commerce/utils/types'
|
||||
|
||||
import type {
|
||||
CartLineInput,
|
||||
CartCreateMutation,
|
||||
CartDetailsFragment,
|
||||
CartCreateMutationVariables,
|
||||
} from '../../schema'
|
||||
|
||||
import Cookies from 'js-cookie'
|
||||
import { throwUserErrors } from './throw-user-errors'
|
||||
import { cartCreateMutation } from './mutations/cart-mutations'
|
||||
|
||||
import {
|
||||
SHOPIFY_CART_ID_COOKIE,
|
||||
SHOPIFY_CART_URL_COOKIE,
|
||||
SHOPIFY_COOKIE_EXPIRE,
|
||||
} from '../const'
|
||||
|
||||
export const setCartUrlCookie = (cartUrl: string) => {
|
||||
if (cartUrl) {
|
||||
const oldCookie = Cookies.get(SHOPIFY_CART_URL_COOKIE)
|
||||
if (oldCookie !== cartUrl) {
|
||||
Cookies.set(SHOPIFY_CART_URL_COOKIE, cartUrl, {
|
||||
expires: SHOPIFY_COOKIE_EXPIRE,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getCartId = (id?: string) => {
|
||||
return id || Cookies.get(SHOPIFY_CART_ID_COOKIE)
|
||||
}
|
||||
|
||||
export const cartCreate = async (
|
||||
fetch: <T = any, B = Body>(options: FetcherOptions<B>) => Promise<T>,
|
||||
lines?: Array<CartLineInput> | CartLineInput
|
||||
): Promise<CartDetailsFragment | null | undefined> => {
|
||||
const { cartCreate } = await fetch<
|
||||
CartCreateMutation,
|
||||
CartCreateMutationVariables
|
||||
>({
|
||||
query: cartCreateMutation,
|
||||
variables: {
|
||||
input: {
|
||||
lines,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const cart = cartCreate?.cart
|
||||
|
||||
throwUserErrors(cartCreate?.userErrors)
|
||||
|
||||
if (cart?.id) {
|
||||
const options = {
|
||||
expires: SHOPIFY_COOKIE_EXPIRE,
|
||||
}
|
||||
Cookies.set(SHOPIFY_CART_ID_COOKIE, cart.id, options)
|
||||
}
|
||||
|
||||
setCartUrlCookie(cart?.checkoutUrl)
|
||||
|
||||
return cart
|
||||
}
|
132
packages/shopify/src/utils/collections.ts
Normal file
132
packages/shopify/src/utils/collections.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import type {
|
||||
CollectionEdge,
|
||||
GetAllProductVendorsQuery,
|
||||
GetAllProductVendorsQueryVariables,
|
||||
} from '../../schema'
|
||||
|
||||
import type { Category } from '../types/site'
|
||||
import type { SearchProductsBody } from '../types/product'
|
||||
|
||||
import { ShopifyConfig } from '../api'
|
||||
import { normalizeCategory } from './normalize'
|
||||
import { getAllProductVendors, getSiteCollectionsQuery } from './queries'
|
||||
|
||||
export const getCategories = async ({
|
||||
fetch,
|
||||
locale,
|
||||
}: ShopifyConfig): Promise<Category[]> => {
|
||||
const { data } = await fetch(
|
||||
getSiteCollectionsQuery,
|
||||
{
|
||||
variables: {
|
||||
first: 250,
|
||||
},
|
||||
},
|
||||
{
|
||||
...(locale && {
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
data.collections?.edges?.map(({ node }: CollectionEdge) =>
|
||||
normalizeCategory(node)
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
export type Brand = {
|
||||
entityId: string
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type BrandEdge = {
|
||||
node: Brand
|
||||
}
|
||||
|
||||
export type Brands = BrandEdge[]
|
||||
|
||||
export const getBrands = async (
|
||||
config: ShopifyConfig
|
||||
): Promise<BrandEdge[]> => {
|
||||
const { data } = await config.fetch<
|
||||
GetAllProductVendorsQuery,
|
||||
GetAllProductVendorsQueryVariables
|
||||
>(getAllProductVendors, {
|
||||
variables: {
|
||||
first: 250,
|
||||
},
|
||||
})
|
||||
|
||||
let vendorsStrings = data.products.edges.map(({ node: { vendor } }) => vendor)
|
||||
|
||||
return [...new Set(vendorsStrings)].map((v) => {
|
||||
const id = v.replace(/\s+/g, '-').toLowerCase()
|
||||
return {
|
||||
node: {
|
||||
entityId: id,
|
||||
name: v,
|
||||
path: `brands/${id}`,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const getSortVariables = (
|
||||
sort?: string,
|
||||
isCategory: boolean = false
|
||||
) => {
|
||||
switch (sort) {
|
||||
case 'price-asc':
|
||||
return {
|
||||
sortKey: 'PRICE',
|
||||
reverse: false,
|
||||
}
|
||||
case 'price-desc':
|
||||
return {
|
||||
sortKey: 'PRICE',
|
||||
reverse: true,
|
||||
}
|
||||
case 'trending-desc':
|
||||
return {
|
||||
sortKey: 'BEST_SELLING',
|
||||
reverse: false,
|
||||
}
|
||||
case 'latest-desc':
|
||||
return {
|
||||
sortKey: isCategory ? 'CREATED' : 'CREATED_AT',
|
||||
reverse: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getSearchVariables = ({
|
||||
brandId,
|
||||
search,
|
||||
categoryId,
|
||||
sort,
|
||||
locale,
|
||||
}: SearchProductsBody) => {
|
||||
let query = ''
|
||||
|
||||
if (search) {
|
||||
query += `product_type:${search} OR title:${search} OR tag:${search} `
|
||||
}
|
||||
|
||||
if (brandId) {
|
||||
query += `${search ? 'AND ' : ''}vendor:${brandId}`
|
||||
}
|
||||
|
||||
return {
|
||||
categoryId,
|
||||
query,
|
||||
...getSortVariables(sort, !!categoryId),
|
||||
...(locale && {
|
||||
locale,
|
||||
}),
|
||||
}
|
||||
}
|
@ -1,303 +0,0 @@
|
||||
import Cookies, { CookieAttributes } from 'js-cookie'
|
||||
|
||||
import type {
|
||||
CartLineInput,
|
||||
CollectionEdge,
|
||||
CartCreateMutation,
|
||||
CartDetailsFragment,
|
||||
CartCreateMutationVariables,
|
||||
GetAllProductVendorsQuery,
|
||||
GetAllProductVendorsQueryVariables,
|
||||
} from '../../schema'
|
||||
|
||||
import type { Category } from '../types/site'
|
||||
import type { MetafieldType, SearchProductsBody } from '../types/product'
|
||||
import type { FetcherOptions } from '@vercel/commerce/utils/types'
|
||||
|
||||
import {
|
||||
SHOPIFY_CART_URL_COOKIE,
|
||||
SHOPIFY_COOKIE_EXPIRE,
|
||||
SHOPIFY_CART_ID_COOKIE,
|
||||
SHOPIFY_CUSTOMER_TOKEN_COOKIE,
|
||||
} from '../const'
|
||||
|
||||
import { ShopifyConfig } from '../api'
|
||||
import { normalizeCategory } from './normalize'
|
||||
import { throwUserErrors } from './throw-user-errors'
|
||||
import { cartCreateMutation } from './mutations/cart-mutations'
|
||||
import { getAllProductVendors, getSiteCollectionsQuery } from './queries'
|
||||
|
||||
export const cartCreate = async (
|
||||
fetch: <T = any, B = Body>(options: FetcherOptions<B>) => Promise<T>,
|
||||
lines?: Array<CartLineInput> | CartLineInput
|
||||
): Promise<CartDetailsFragment | null | undefined> => {
|
||||
const { cartCreate } = await fetch<
|
||||
CartCreateMutation,
|
||||
CartCreateMutationVariables
|
||||
>({
|
||||
query: cartCreateMutation,
|
||||
variables: {
|
||||
input: {
|
||||
lines,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const cart = cartCreate?.cart
|
||||
|
||||
throwUserErrors(cartCreate?.userErrors)
|
||||
|
||||
if (cart?.id) {
|
||||
const options = {
|
||||
expires: SHOPIFY_COOKIE_EXPIRE,
|
||||
}
|
||||
Cookies.set(SHOPIFY_CART_ID_COOKIE, cart.id, options)
|
||||
}
|
||||
|
||||
setCartUrlCookie(cart?.checkoutUrl)
|
||||
|
||||
return cart
|
||||
}
|
||||
|
||||
export const getCategories = async ({
|
||||
fetch,
|
||||
locale,
|
||||
}: ShopifyConfig): Promise<Category[]> => {
|
||||
const { data } = await fetch(
|
||||
getSiteCollectionsQuery,
|
||||
{
|
||||
variables: {
|
||||
first: 250,
|
||||
},
|
||||
},
|
||||
{
|
||||
...(locale && {
|
||||
headers: {
|
||||
'Accept-Language': locale,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
data.collections?.edges?.map(({ node }: CollectionEdge) =>
|
||||
normalizeCategory(node)
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
export type Brand = {
|
||||
entityId: string
|
||||
name: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type BrandEdge = {
|
||||
node: Brand
|
||||
}
|
||||
|
||||
export type Brands = BrandEdge[]
|
||||
|
||||
export const getBrands = async (
|
||||
config: ShopifyConfig
|
||||
): Promise<BrandEdge[]> => {
|
||||
const { data } = await config.fetch<
|
||||
GetAllProductVendorsQuery,
|
||||
GetAllProductVendorsQueryVariables
|
||||
>(getAllProductVendors, {
|
||||
variables: {
|
||||
first: 250,
|
||||
},
|
||||
})
|
||||
|
||||
let vendorsStrings = data.products.edges.map(({ node: { vendor } }) => vendor)
|
||||
|
||||
return [...new Set(vendorsStrings)].map((v) => {
|
||||
const id = v.replace(/\s+/g, '-').toLowerCase()
|
||||
return {
|
||||
node: {
|
||||
entityId: id,
|
||||
name: v,
|
||||
path: `brands/${id}`,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const setCartUrlCookie = (cartUrl: string) => {
|
||||
if (cartUrl) {
|
||||
const oldCookie = Cookies.get(SHOPIFY_CART_URL_COOKIE)
|
||||
if (oldCookie !== cartUrl) {
|
||||
Cookies.set(SHOPIFY_CART_URL_COOKIE, cartUrl, {
|
||||
expires: SHOPIFY_COOKIE_EXPIRE,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getCartId = (id?: string) => {
|
||||
return id || Cookies.get(SHOPIFY_CART_ID_COOKIE)
|
||||
}
|
||||
|
||||
export const getSortVariables = (
|
||||
sort?: string,
|
||||
isCategory: boolean = false
|
||||
) => {
|
||||
switch (sort) {
|
||||
case 'price-asc':
|
||||
return {
|
||||
sortKey: 'PRICE',
|
||||
reverse: false,
|
||||
}
|
||||
case 'price-desc':
|
||||
return {
|
||||
sortKey: 'PRICE',
|
||||
reverse: true,
|
||||
}
|
||||
case 'trending-desc':
|
||||
return {
|
||||
sortKey: 'BEST_SELLING',
|
||||
reverse: false,
|
||||
}
|
||||
case 'latest-desc':
|
||||
return {
|
||||
sortKey: isCategory ? 'CREATED' : 'CREATED_AT',
|
||||
reverse: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getSearchVariables = ({
|
||||
brandId,
|
||||
search,
|
||||
categoryId,
|
||||
sort,
|
||||
locale,
|
||||
}: SearchProductsBody) => {
|
||||
let query = ''
|
||||
|
||||
if (search) {
|
||||
query += `product_type:${search} OR title:${search} OR tag:${search} `
|
||||
}
|
||||
|
||||
if (brandId) {
|
||||
query += `${search ? 'AND ' : ''}vendor:${brandId}`
|
||||
}
|
||||
|
||||
return {
|
||||
categoryId,
|
||||
query,
|
||||
...getSortVariables(sort, !!categoryId),
|
||||
...(locale && {
|
||||
locale,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export const getCustomerToken = () => Cookies.get(SHOPIFY_CUSTOMER_TOKEN_COOKIE)
|
||||
|
||||
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 ?? {
|
||||
expires: SHOPIFY_COOKIE_EXPIRE,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const parseJson = (value: string): any => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const unitConversion: Record<string, string> = {
|
||||
MILLIMETERS: 'millimeter',
|
||||
CENTIMETERS: 'centimeter',
|
||||
METERS: 'meter',
|
||||
|
||||
MILLILITERS: 'milliliter',
|
||||
LITERS: 'liter',
|
||||
FLUID_OUNCES: 'fluid-ounce',
|
||||
IMPERIAL_FLUID_OUNCES: 'fluid-ounce',
|
||||
GALLONS: 'gallon',
|
||||
|
||||
KILOGRAMS: 'kilogram',
|
||||
GRAMS: 'gram',
|
||||
OUNCES: 'ounce',
|
||||
POUNDS: 'pound',
|
||||
}
|
||||
|
||||
export const getMeasurment = (input: string, locale: string = 'en-US') => {
|
||||
try {
|
||||
let { unit, value } = JSON.parse(input)
|
||||
|
||||
return new Intl.NumberFormat(locale, {
|
||||
unit: unitConversion[unit],
|
||||
style: 'unit',
|
||||
}).format(parseFloat(value))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
export const getMetafieldValue = (
|
||||
type: MetafieldType,
|
||||
value: string,
|
||||
locale: string = 'en-US'
|
||||
) => {
|
||||
switch (type) {
|
||||
case 'boolean':
|
||||
return value === 'true' ? '✓' : 'No'
|
||||
case 'number_integer':
|
||||
return parseInt(value).toLocaleString(locale)
|
||||
case 'number_decimal':
|
||||
return parseFloat(value).toLocaleString(locale)
|
||||
case 'date':
|
||||
return Intl.DateTimeFormat(locale, {
|
||||
dateStyle: 'medium',
|
||||
}).format(new Date(value))
|
||||
case 'date_time':
|
||||
return Intl.DateTimeFormat(locale, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'long',
|
||||
}).format(new Date(value))
|
||||
case 'dimension':
|
||||
case 'volume':
|
||||
case 'weight':
|
||||
return getMeasurment(value, locale)
|
||||
case 'rating':
|
||||
const { scale_max, value: val } = JSON.parse(value)
|
||||
return Array.from({ length: scale_max }, (_, i) =>
|
||||
i <= val - 1 ? '★' : '☆'
|
||||
).join('')
|
||||
case 'color':
|
||||
return `<figure style="background-color: ${value}; width: 1rem; height:1rem; display:block; margin-top: 2px; border-radius: 100%;"/>`
|
||||
case 'url':
|
||||
return `<a href="${value}" target="_blank">${value}</a>`
|
||||
case 'multi_line_text_field':
|
||||
return value
|
||||
.split('\n')
|
||||
.map((line) => `<p>${line}</p>`)
|
||||
.join('')
|
||||
case 'json':
|
||||
case 'single_line_text_field':
|
||||
case 'product_reference':
|
||||
case 'page_reference':
|
||||
case 'variant_reference':
|
||||
case 'file_reference':
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
@ -5,9 +5,12 @@ export {
|
||||
handleLogin,
|
||||
handleAutomaticLogin,
|
||||
handleAccountActivation,
|
||||
} from './handle-login'
|
||||
} from './auth'
|
||||
|
||||
export * from './helpers'
|
||||
export * from './auth'
|
||||
export * from './cart'
|
||||
export * from './metafields'
|
||||
export * from './collections'
|
||||
export * from './queries'
|
||||
export * from './mutations'
|
||||
export * from './normalize'
|
||||
|
90
packages/shopify/src/utils/metafields.ts
Normal file
90
packages/shopify/src/utils/metafields.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { MetafieldType } from '../types/product'
|
||||
|
||||
export const parseJson = (value: string): any => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
const unitConversion: Record<string, string> = {
|
||||
MILLIMETERS: 'millimeter',
|
||||
CENTIMETERS: 'centimeter',
|
||||
METERS: 'meter',
|
||||
|
||||
MILLILITERS: 'milliliter',
|
||||
LITERS: 'liter',
|
||||
FLUID_OUNCES: 'fluid-ounce',
|
||||
IMPERIAL_FLUID_OUNCES: 'fluid-ounce',
|
||||
GALLONS: 'gallon',
|
||||
|
||||
KILOGRAMS: 'kilogram',
|
||||
GRAMS: 'gram',
|
||||
OUNCES: 'ounce',
|
||||
POUNDS: 'pound',
|
||||
}
|
||||
|
||||
export const getMeasurment = (input: string, locale: string = 'en-US') => {
|
||||
try {
|
||||
let { unit, value } = JSON.parse(input)
|
||||
|
||||
return new Intl.NumberFormat(locale, {
|
||||
unit: unitConversion[unit],
|
||||
style: 'unit',
|
||||
}).format(parseFloat(value))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return input
|
||||
}
|
||||
}
|
||||
|
||||
export const getMetafieldValue = (
|
||||
type: MetafieldType,
|
||||
value: string,
|
||||
locale: string = 'en-US'
|
||||
) => {
|
||||
switch (type) {
|
||||
case 'boolean':
|
||||
return value === 'true' ? '✓' : 'No'
|
||||
case 'number_integer':
|
||||
return parseInt(value).toLocaleString(locale)
|
||||
case 'number_decimal':
|
||||
return parseFloat(value).toLocaleString(locale)
|
||||
case 'date':
|
||||
return Intl.DateTimeFormat(locale, {
|
||||
dateStyle: 'medium',
|
||||
}).format(new Date(value))
|
||||
case 'date_time':
|
||||
return Intl.DateTimeFormat(locale, {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'long',
|
||||
}).format(new Date(value))
|
||||
case 'dimension':
|
||||
case 'volume':
|
||||
case 'weight':
|
||||
return getMeasurment(value, locale)
|
||||
case 'rating':
|
||||
const { scale_max, value: val } = JSON.parse(value)
|
||||
return Array.from({ length: scale_max }, (_, i) =>
|
||||
i <= val - 1 ? '★' : '☆'
|
||||
).join('')
|
||||
case 'color':
|
||||
return `<figure style="background-color: ${value}; width: 1rem; height:1rem; display:block; margin-top: 2px; border-radius: 100%;"/>`
|
||||
case 'url':
|
||||
return `<a href="${value}" target="_blank">${value}</a>`
|
||||
case 'multi_line_text_field':
|
||||
return value
|
||||
.split('\n')
|
||||
.map((line) => `<p>${line}</p>`)
|
||||
.join('')
|
||||
case 'json':
|
||||
case 'single_line_text_field':
|
||||
case 'product_reference':
|
||||
case 'page_reference':
|
||||
case 'variant_reference':
|
||||
case 'file_reference':
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ import humanizeString from 'humanize-string'
|
||||
import { CommerceError } from '@vercel/commerce/utils/errors'
|
||||
|
||||
import { colorMap } from './colors'
|
||||
import { getMetafieldValue, parseJson } from './helpers'
|
||||
import { getMetafieldValue, parseJson } from './metafields'
|
||||
|
||||
type MoneyProps = MoneyV2 & { retailPrice?: string | number }
|
||||
|
||||
|
@ -5,6 +5,6 @@
|
||||
"wishlist": false,
|
||||
"customerAuth": false,
|
||||
"customCheckout": false,
|
||||
"metafields": false
|
||||
"customFields": false
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import cn from 'clsx'
|
||||
import Image from 'next/image'
|
||||
import { WishlistButton } from '@components/wishlist'
|
||||
import ProductSidebar from '../ProductSidebar'
|
||||
import ProductTag from '../ProductTag'
|
||||
|
||||
import { useProduct } from '../context'
|
||||
import { WishlistButton } from '@components/wishlist'
|
||||
|
||||
import ProductTag from '../ProductTag'
|
||||
import ProductSlider from '../ProductSlider'
|
||||
import ProductSidebar from '../ProductSidebar'
|
||||
|
||||
import s from './ProductDetails.module.css'
|
||||
import ProductSlider from '../ProductSlider'
|
||||
|
||||
const ProductDetails = () => {
|
||||
const { product, variant, price } = useProduct()
|
||||
|
@ -1,12 +1,14 @@
|
||||
import s from './ProductView.module.css'
|
||||
import { FC } from 'react'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type { Product } from '@commerce/types/product'
|
||||
|
||||
import { ProductCard } from '@components/product'
|
||||
import { Container, Text } from '@components/ui'
|
||||
import { ProductProvider } from '../context'
|
||||
import ProductDetails from '../ProductDetails/ProductDetails'
|
||||
import { SEO } from '@components/common'
|
||||
import { ProductProvider } from '../context'
|
||||
import { Container, Text } from '@components/ui'
|
||||
import { ProductCard } from '@components/product'
|
||||
|
||||
import ProductDetails from '../ProductDetails/ProductDetails'
|
||||
|
||||
interface ProductViewProps {
|
||||
product: Product
|
||||
|
@ -1,28 +1,29 @@
|
||||
import { Product, ProductImage, ProductVariant } from '@commerce/types/product'
|
||||
import { useMemo, useState, useEffect, useContext, createContext } from 'react'
|
||||
|
||||
import {
|
||||
getProductVariant,
|
||||
selectDefaultOptionFromProduct,
|
||||
SelectedOptions,
|
||||
} from './helpers'
|
||||
import type { FC, ReactNode, Dispatch, SetStateAction } from 'react'
|
||||
|
||||
import React, { FC, useMemo, useState, ReactNode, useEffect } from 'react'
|
||||
import type {
|
||||
Product,
|
||||
ProductImage,
|
||||
ProductVariant,
|
||||
} from '@commerce/types/product'
|
||||
|
||||
import type { SelectedOptions } from './helpers'
|
||||
|
||||
import usePrice from '@framework/product/use-price'
|
||||
import { getProductVariant, selectDefaultOptionFromProduct } from './helpers'
|
||||
|
||||
export interface ProductContextValue {
|
||||
product: Product
|
||||
imageIndex: number | null
|
||||
setImageIndex: React.Dispatch<React.SetStateAction<number | null>>
|
||||
setImageIndex: Dispatch<SetStateAction<number | null>>
|
||||
price: string
|
||||
variant: ProductVariant
|
||||
selectedOptions: SelectedOptions
|
||||
setSelectedOptions: React.Dispatch<React.SetStateAction<SelectedOptions>>
|
||||
setSelectedOptions: Dispatch<SetStateAction<SelectedOptions>>
|
||||
}
|
||||
|
||||
export const ProductContext = React.createContext<ProductContextValue | null>(
|
||||
null
|
||||
)
|
||||
export const ProductContext = createContext<ProductContextValue | null>(null)
|
||||
|
||||
ProductContext.displayName = 'ProductContext'
|
||||
|
||||
@ -55,11 +56,11 @@ export const ProductProvider: FC<ProductProviderProps> = ({
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const idx = product.images.findIndex((image: ProductImage) => {
|
||||
const index = product.images.findIndex((image: ProductImage) => {
|
||||
return image.url === variant?.image?.url
|
||||
})
|
||||
if (idx !== -1) {
|
||||
setImageIndex(idx)
|
||||
if (index !== -1) {
|
||||
setImageIndex(index)
|
||||
}
|
||||
}, [variant, product])
|
||||
|
||||
@ -82,7 +83,7 @@ export const ProductProvider: FC<ProductProviderProps> = ({
|
||||
}
|
||||
|
||||
export const useProduct = () => {
|
||||
const context = React.useContext(ProductContext) as ProductContextValue
|
||||
const context = useContext(ProductContext) as ProductContextValue
|
||||
if (context === undefined) {
|
||||
throw new Error(`useProduct must be used within a ProductProvider`)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user