Continue Migration, almost done

This commit is contained in:
Daniele Pancottini 2022-12-20 19:15:40 +01:00
parent 15faacc7de
commit e5ebf60e83
11 changed files with 290 additions and 196 deletions

View File

@ -160,7 +160,6 @@ export interface Product {
/**
* The product's base price. Could be the minimum value, or default variant price.
*/
metafields: ProductMetafield[]
price: ProductPrice
/**
* List of product's options.
@ -170,6 +169,10 @@ export interface Product {
* The products vendor name.
*/
vendor?: string
/**
* List of product's media
*/
media: ProductMedia[]
}
export interface SearchProductsBody {
@ -266,3 +269,25 @@ export type GetProductOperation = {
withMetafields?: MetafieldsIdentifiers
}
}
export type ProductPreviewMediaImage = {
altText: string
height: string
id: string
width: string
}
export type ProductMediaSource = {
filesize: number
format: string
mimeType: string
url: string
}
export type ProductMedia = {
alt: string
id: string
mediaContentType: string
previewImage: ProductPreviewMediaImage
sources: ProductMediaSource[]
}

View File

@ -19,6 +19,8 @@ import type {
Collection,
Maybe,
Metafield as ShopifyMetafield,
MediaConnection,
Model3d,
} from '../../schema'
import { colorMap } from './colors'
@ -112,6 +114,7 @@ export function normalizeProduct(
priceRange,
options,
metafields,
media,
...rest
}: ShopifyProduct,
locale?: string
@ -131,6 +134,7 @@ export function normalizeProduct(
.map((o) => normalizeProductOption(o))
: [],
metafields: normalizeMetafields(metafields, locale),
media: media ? normalizeProductMedia(media) : [],
description: description || '',
...(descriptionHtml && { descriptionHtml }),
...rest,
@ -256,3 +260,18 @@ export const normalizeMetafieldValue = (
}
return getMetafieldValue(type, value, locale)
}
const normalizeProductMedia = ({ edges }: MediaConnection) => {
return edges
.filter(({ node }) => Object.keys(node).length !== 0)
.map(({ node }) => {
return {
sources: (node as Model3d).sources.map(({ format, url }) => {
return {
format: format,
url: url,
}
}),
}
})
}

View File

@ -83,6 +83,7 @@ const getProductQuery = /* GraphQL */ `
}
}
}
}
metafields(identifiers: $withMetafields) {
key
value

43
pnpm-lock.yaml generated
View File

@ -657,6 +657,7 @@ importers:
tabbable: ^5.2.1
tailwindcss: ^3.0.13
typescript: 4.7.4
yet-another-react-lightbox: ^2.2.3
dependencies:
'@chakra-ui/icons': 2.0.14_react@18.2.0
'@chakra-ui/react': 2.4.4_4krdlvlqq3ittuocexwl336v2q
@ -702,6 +703,7 @@ importers:
screenfull: 6.0.2
tabbable: 5.3.3
tailwindcss: 3.1.8_postcss@8.4.16
yet-another-react-lightbox: 2.2.3_biqbaboplfbrettd7655fr4n2y
devDependencies:
'@next/bundle-analyzer': 12.3.0
'@types/body-scroll-lock': 3.1.0
@ -3497,7 +3499,18 @@ packages:
- supports-color
dev: false
<<<<<<< HEAD
/@manifoldco/swagger-to-ts/2.1.0:
resolution: {integrity: sha512-IH0FAHhwWHR3Gs3rnVHNEscZujGn+K6/2Zu5cWfZre3Vz2tx1SvvJKEbSM89MztfDDRjOpb+6pQD/vqdEoTBVg==}
engines: {node: '>= 10.0.0'}
deprecated: This package has changed to openapi-typescript
hasBin: true
dependencies:
chalk: 4.1.2
js-yaml: 3.14.1
meow: 7.1.1
prettier: 2.7.1
dev: true
/@motionone/animation/10.15.1:
resolution: {integrity: sha512-mZcJxLjHor+bhcPuIFErMDNyrdb2vJur8lSfMCsuCB4UyV8ILZLvK+t+pg56erv8ud9xQGK/1OGPt10agPrCyQ==}
dependencies:
@ -3544,19 +3557,6 @@ packages:
hey-listen: 1.0.8
tslib: 2.4.0
dev: false
=======
/@manifoldco/swagger-to-ts/2.1.0:
resolution: {integrity: sha512-IH0FAHhwWHR3Gs3rnVHNEscZujGn+K6/2Zu5cWfZre3Vz2tx1SvvJKEbSM89MztfDDRjOpb+6pQD/vqdEoTBVg==}
engines: {node: '>= 10.0.0'}
deprecated: This package has changed to openapi-typescript
hasBin: true
dependencies:
chalk: 4.1.2
js-yaml: 3.14.1
meow: 7.1.1
prettier: 2.7.1
dev: true
>>>>>>> 1ba9d3bd6e79da1f0b05df2f1c0c1ca3632b6c16
/@next/bundle-analyzer/12.3.0:
resolution: {integrity: sha512-hzRLHIrtwOiGEku9rmG7qZk+OQhnqQOL+ycl2XrjBaztBN/xaqnjoG4+HEf9L7ELN943BR+K/ZlaF2OEgbGm+Q==}
@ -7117,7 +7117,6 @@ packages:
tslib: 2.4.0
dev: true
<<<<<<< HEAD
/hey-listen/1.0.8:
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
dev: false
@ -7127,11 +7126,10 @@ packages:
dependencies:
react-is: 16.13.1
dev: false
=======
/hosted-git-info/2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
>>>>>>> 1ba9d3bd6e79da1f0b05df2f1c0c1ca3632b6c16
/http-cache-semantics/4.1.0:
resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==}
@ -11415,6 +11413,17 @@ packages:
yargs-parser: 21.1.1
dev: true
/yet-another-react-lightbox/2.2.3_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-wTjXSqcKZfmudXQqZxe24SqzBAaK5x686CMNMAfNOrbmtyUACCrXTM78899DwtkEYMCBAGvgZc/2GOVu+EAtYA==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
dev: false
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}

View File

@ -7,40 +7,44 @@ import {
Text,
Stack,
Link,
} from '@chakra-ui/react';
import NextLink from "next/link"
import { Product } from '@commerce/types';
} from '@chakra-ui/react'
import NextLink from 'next/link'
import { Product } from '@commerce/types'
import style from './ProductCardRoomStyle.module.css';
import style from './ProductCardRoomStyle.module.css'
export default function ProductCardRoom(props: {
product: Product.Product,
product: Product.Product
decade: string
}) {
let historicDescription = props.product.metafields
.filter(meta => meta.key == 'descrizione_storica')
.map(meta => meta.value);
let technicalDescription = props.product.metafields
.filter(meta => meta.key == 'descrizione_tecnica')
.map(meta => meta.value);
let nationOrigin = props.product.metafields
.filter(meta => meta.key == 'nazionalit_')
.map(meta => meta.value);
let historicDescription =
props.product.metafields!.custom.descrizione_storica.value
let technicalDescription =
props.product.metafields!.custom.descrizione_tecnica.value
let nationOrigin = props.product.metafields!.custom.nazionalit_.value
return (
<Flex w="full" alignItems="center" justifyContent="center" direction={'row'}>
<Flex
w="full"
alignItems="center"
justifyContent="center"
direction={'row'}
>
<Box
maxW={'445px'}
w={'full'}
boxShadow={'2xl'}
rounded={'md'}
overflow={'hidden'}
className={style.cardBody}>
className={style.cardBody}
>
<Image
className={style.flagIcon}
src={'http://purecatamphetamine.github.io/country-flag-icons/3x2/' + nationOrigin + '.svg'}
src={
'http://purecatamphetamine.github.io/country-flag-icons/3x2/' +
nationOrigin +
'.svg'
}
alt={`Picture of Flag`}
rounded={'lg'}
/>
@ -51,17 +55,11 @@ export default function ProductCardRoom(props: {
alt={`Picture of Decade`}
/>
<Box
className={style.imageContainer}
w={'full'}
height={'220px'}
>
<Box className={style.imageContainer} w={'full'} height={'220px'}>
<NextLink href={'/product/' + props.product.slug} passHref>
<Link style={{textDecoration: 'none', height: 'inherit'}}>
<Link style={{ textDecoration: 'none', height: 'inherit' }}>
<Image
src={
props.product.images[0].url
}
src={props.product.images[0].url}
objectFit={'cover'}
margin={'auto'}
height={'inherit'}
@ -70,20 +68,28 @@ export default function ProductCardRoom(props: {
</NextLink>
</Box>
<Box
p={5}
className={style.captionContainer}>
<Box p={5} className={style.captionContainer}>
<Stack align={'center'}>
<Heading fontSize={'2xl'} textAlign={'center'} fontFamily={'body'} fontWeight={500}>
<Heading
fontSize={'2xl'}
textAlign={'center'}
fontFamily={'body'}
fontWeight={500}
>
{props.product.name}
</Heading>
</Stack>
<Stack mt={6} align={'center'}>
<Divider borderColor={'blackAlpha.600'} />
{historicDescription.pop()?.split('\n').map((line, index) => (
<Text key={index} padding={0} color={'gray.500'} fontSize={'sm'} align={'center'}>
{historicDescription.split('\n').map((line, index) => (
<Text
key={index}
padding={0}
color={'gray.500'}
fontSize={'sm'}
align={'center'}
>
{line}
</Text>
))}
@ -93,8 +99,7 @@ export default function ProductCardRoom(props: {
</Text>
</Stack>
</Box>
</Box>
</Flex>
);
)
}

View File

@ -3,15 +3,15 @@ import { useAddItem } from '@framework/cart'
import { FC, useEffect, useState } from 'react'
import { ProductOptions } from '@components/product'
import type { Product } from '@commerce/types/product'
import { Button, Text, Rating, Collapse, useUI } from '@components/ui'
import { Button, Rating, Collapse, Text, useUI } from '@components/ui'
import {
getProductVariant,
selectDefaultOptionFromProduct,
SelectedOptions,
} from '../helpers'
import ErrorMessage from '@components/ui/ErrorMessage'
import { ProductCustomFields } from '../ProductCustomFields'
import { ProductMetafields } from '../ProductMetafields'
import { Box, Stack, Text as ChakraText } from '@chakra-ui/react'
import productDetailsMetafields from '../../../static_data/productDetailsMetafields.json'
interface ProductSidebarProps {
product: Product
@ -20,9 +20,8 @@ interface ProductSidebarProps {
const ProductSidebar: FC<ProductSidebarProps> = ({ product, className }) => {
const addItem = useAddItem()
const { openSidebar, setSidebarView } = useUI()
const { openSidebar } = useUI()
const [loading, setLoading] = useState(false)
const [error, setError] = useState<null | Error>(null)
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({})
useEffect(() => {
@ -32,27 +31,20 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ product, className }) => {
const variant = getProductVariant(product, selectedOptions)
const addToCart = async () => {
setLoading(true)
setError(null)
try {
await addItem({
productId: String(product.id),
variantId: String(variant ? variant.id : product.variants[0]?.id),
})
setSidebarView('CART_VIEW')
openSidebar()
setLoading(false)
} catch (err) {
setLoading(false)
if (err instanceof Error) {
console.error(err)
setError({
...err,
message: 'Could not add item to cart. Please try again.',
})
}
}
}
console.log(product.metafields!.custom)
return (
<div className={className}>
<ProductOptions
@ -60,22 +52,31 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ product, className }) => {
selectedOptions={selectedOptions}
setSelectedOptions={setSelectedOptions}
/>
<Text
className="pb-4 break-words w-full max-w-xl"
html={product.descriptionHtml || product.description}
/>
{product.metafields?.reviews?.rating && (
<div className="flex flex-row justify-between items-center">
<Rating value={product.metafields.reviews.rating.value} />
<div className="text-accent-6 pr-1 font-medium text-sm">
{product.metafields.reviews.count?.value ?? 0} reviews
</div>
</div>
)}
{/* Product Description With Metafields */}
<div>
{error && <ErrorMessage error={error} className="my-5" />}
<Box>
<Stack>
{productDetailsMetafields.metafields[0].names.map((meta) => (
<Box key={meta.key}>
<ChakraText
as={'span'}
textTransform={'uppercase'}
fontWeight={'bold'}
>
{meta.name}:{' '}
</ChakraText>
<ChakraText as={'span'}>
{product.metafields.custom
.filter((o) => o.key == meta.key)
.map((o) => o.value)}
</ChakraText>
</Box>
))}
</Stack>
</Box>
<div style={{ marginTop: 20 }}>
{process.env.COMMERCE_CART_ENABLED && (
<Button
aria-label="Add to Cart"
@ -91,33 +92,6 @@ const ProductSidebar: FC<ProductSidebarProps> = ({ product, className }) => {
</Button>
)}
</div>
<div className="mt-6">
<Collapse title="Care">
This is a limited edition production run. Printing starts when the
drop ends.
</Collapse>
<Collapse title="Details">
This is a limited edition production run. Printing starts when the
drop ends. Reminder: Bad Boys For Life. Shipping may take 10+ days due
to COVID-19.
</Collapse>
{product.customFields && product.customFields?.length > 0 && (
<Collapse title="Specifications">
<ProductCustomFields customFields={product.customFields} />
</Collapse>
)}
{product.metafields?.my_fields && (
<Collapse title="Specifications">
<ProductMetafields
metafields={product.metafields}
namespace="my_fields"
/>
</Collapse>
)}
</div>
</div>
)
}

View File

@ -1,7 +1,7 @@
import cn from 'clsx'
import Image from 'next/image'
import s from './ProductView.module.css'
import { FC } from 'react'
import { FC, useState } from 'react'
import type { Product } from '@commerce/types/product'
import usePrice from '@framework/product/use-price'
import { WishlistButton } from '@components/wishlist'
@ -10,6 +10,10 @@ import { Container, Text } from '@components/ui'
import { SEO } from '@components/common'
import ProductSidebar from '../ProductSidebar'
import ProductTag from '../ProductTag'
import ProductModel from '../ProductModel/ProductModel'
import Lightbox from 'yet-another-react-lightbox'
import 'yet-another-react-lightbox/styles.css'
interface ProductViewProps {
product: Product
relatedProducts: Product[]
@ -22,6 +26,18 @@ const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
currencyCode: product.price.currencyCode!,
})
const model3dPath = product.media
.map((media) => {
return media.sources
.filter((source) => source.format == 'glb')
.map((source) => source.url)
.slice(0)
})
.pop()
?.pop()
const [isLightboxOpen, setLightboxOpen] = useState(false)
return (
<>
<Container className="max-w-none w-full" clean>
@ -37,6 +53,7 @@ const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
{product.images.map((image, i) => (
<div key={image.url} className={s.imageContainer}>
<Image
id={'product-image-' + i}
className={s.img}
src={image.url!}
alt={image.alt || 'Product Image'}
@ -44,10 +61,28 @@ const ProductView: FC<ProductViewProps> = ({ product, relatedProducts }) => {
height={600}
priority={i === 0}
quality="85"
style={{ cursor: 'pointer' }}
onClick={() => setLightboxOpen(true)}
/>
</div>
))}
{model3dPath != undefined ? (
<div key={'model3d'} className={s.imageContainer}>
<ProductModel modelPath={model3dPath}></ProductModel>
</div>
) : (
<></>
)}
</ProductSlider>
<Lightbox
open={isLightboxOpen}
close={() => setLightboxOpen(false)}
slides={product.images.map((image) => {
return {
src: image.url,
}
})}
/>
</div>
{process.env.COMMERCE_WISHLIST_ENABLED && (
<WishlistButton

View File

@ -56,7 +56,8 @@
"react-use-measure": "^2.1.1",
"screenfull": "^6.0.2",
"tabbable": "^5.2.1",
"tailwindcss": "^3.0.13"
"tailwindcss": "^3.0.13",
"yet-another-react-lightbox": "^2.2.3"
},
"devDependencies": {
"@next/bundle-analyzer": "^12.0.8",

View File

@ -22,6 +22,7 @@ import MarkerCardModal from '@components/common/Room/MarkerCardModal/MarkerCardM
import { useDisclosure } from '@chakra-ui/react'
import decadesManifest from '../../../static_data/decadesManifest.json'
import productDetailsMetafields from '../../../static_data/productDetailsMetafields.json'
import {
MarkerData,
MarkerJson,
@ -123,7 +124,14 @@ export async function getStaticProps({
continue
const productPromise = commerce.getProduct({
variables: { slug: productMarker.markerSource },
variables: {
slug: productMarker.markerSource,
withMetafields: [
{ namespace: 'custom', key: 'nazionalit_' },
{ namespace: 'custom', key: 'descrizione_tecnica' },
{ namespace: 'custom', key: 'descrizione_storica' },
],
},
config,
preview,
})
@ -132,8 +140,6 @@ export async function getStaticProps({
}
}
console.log(products)
if (!products) {
throw new Error(`Products associated with markers not found`)
}

View File

@ -10,14 +10,16 @@ import { useRouter } from 'next/router'
import { Layout } from '@components/common'
import { ProductView } from '@components/product'
// Used by the Shopify Example
const withMetafields = [
{ namespace: 'reviews', key: 'rating' },
{ namespace: 'reviews', key: 'count' },
{ namespace: 'my_fields', key: 'width' },
{ namespace: 'my_fields', key: 'weight' },
{ namespace: 'my_fields', key: 'length' },
]
import productDetailsMetafields from '../../static_data/productDetailsMetafields.json'
const withMetafields = productDetailsMetafields.metafields[0].names.map(
(metafield: any) => {
return {
namespace: metafield.namespace,
key: metafield.key,
}
}
)
export async function getStaticProps({
params,
@ -28,6 +30,9 @@ export async function getStaticProps({
const config = { locale, locales }
const pagesPromise = commerce.getAllPages({ config, preview })
const siteInfoPromise = commerce.getSiteInfo({ config, preview })
console.log(withMetafields)
const productPromise = commerce.getProduct({
variables: {
slug: params!.slug,
@ -47,6 +52,8 @@ export async function getStaticProps({
const { product } = await productPromise
const { products: relatedProducts } = await allProductsPromise
console.log(product)
if (!product) {
return {
notFound: true,

View File

@ -5,51 +5,63 @@
"names": [
{
"name": "Tipo",
"key": "tipo"
"key": "tipo",
"namespace": "custom"
},
{
"name": "Materiale",
"key": "materiale"
"key": "materiale",
"namespace": "custom"
},
{
"name": "Colore",
"key": "colore"
"key": "colore",
"namespace": "custom"
},
{
"name": "Condizioni Estetiche",
"key": "condizioni_estetiche"
"key": "condizioni_estetiche",
"namespace": "custom"
},
{
"name": "Altezza",
"key": "altezza"
"key": "altezza",
"namespace": "custom"
},
{
"name": "Spessore",
"key": "spessore"
"key": "spessore",
"namespace": "custom"
},
{
"name": "Larghezza",
"key": "larghezza"
"key": "larghezza",
"namespace": "custom"
},
{
"name": "Peso",
"key": "peso"
"key": "peso",
"namespace": "custom"
},
{
"name": "Confezione e Accessori Originali",
"key": "confezione_accessori"
"key": "confezione_accessori",
"namespace": "custom"
},
{
"name": "Descrizione",
"key": "descrizione"
"key": "descrizione",
"namespace": "custom"
},
{
"name": "Vintage",
"key": "vintage"
"key": "vintage",
"namespace": "custom"
},
{
"name": "Made In",
"key": "made_in"
"key": "made_in",
"namespace": "custom"
}
]
}