diff --git a/.github/workflows/oxygen-deployment-1000010106.yml b/.github/workflows/oxygen-deployment-1000010106.yml index 6721c2c..beb61e9 100644 --- a/.github/workflows/oxygen-deployment-1000010106.yml +++ b/.github/workflows/oxygen-deployment-1000010106.yml @@ -36,7 +36,7 @@ jobs: ${{ runner.os }}- - name: Install dependencies - run: npm install + run: npm ci - name: Build and Publish to Oxygen id: deploy diff --git a/.npmrc b/.npmrc index 781b152..424fb04 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1,5 @@ @weaverse:registry=https://registry.npmjs.com @shopify:registry=https://registry.npmjs.com +progress=false legacy-peer-deps=true diff --git a/app/components/Container.tsx b/app/components/Container.tsx deleted file mode 100644 index f0f97cf..0000000 --- a/app/components/Container.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import clsx from "clsx"; - -interface ContainerProps { - children: React.ReactNode; - className?: string; -} - -export function Container(props: ContainerProps) { - let { children, className } = props; - return ( -
- {children} -
- ); -} \ No newline at end of file diff --git a/app/sections/single-product/judgeme-review.tsx b/app/components/product-form/judgeme-review.tsx similarity index 76% rename from app/sections/single-product/judgeme-review.tsx rename to app/components/product-form/judgeme-review.tsx index 320fb0f..e6983d6 100644 --- a/app/sections/single-product/judgeme-review.tsx +++ b/app/components/product-form/judgeme-review.tsx @@ -4,29 +4,32 @@ import type { } from '@weaverse/hydrogen'; import StarsRating from 'react-star-rate'; import {useParentInstance} from '@weaverse/hydrogen'; -import {useFetcher} from '@remix-run/react'; +import {useFetcher, useLoaderData} from '@remix-run/react'; import {forwardRef, useEffect} from 'react'; import {usePrefixPathWithLocale} from '~/lib/utils'; +type JudgemeReviewsData = { + rating: number; + reviewNumber: number; + error?: string; +}; let JudgemeReview = forwardRef( (props, ref) => { - let {load, data} = useFetcher<{ - rating: number; - reviewNumber: number; - error?: string; + let loaderData = useLoaderData<{ + judgemeReviews: JudgemeReviewsData; }>(); - + let judgemeReviews = loaderData?.judgemeReviews; + let {load, data: fetchData} = useFetcher(); let context = useParentInstance(); let handle = context?.data?.product?.handle!; let api = usePrefixPathWithLocale(`/api/review/${handle}`); useEffect(() => { - if (handle) { - load(api); - } + if (judgemeReviews || !handle) return; + load(api); // eslint-disable-next-line react-hooks/exhaustive-deps }, [handle, api]); - + let data = judgemeReviews || fetchData; if (!data) return null; if (data.error) { return ( diff --git a/app/sections/single-product/options.tsx b/app/components/product-form/options.tsx similarity index 100% rename from app/sections/single-product/options.tsx rename to app/components/product-form/options.tsx diff --git a/app/sections/single-product/placeholder.tsx b/app/components/product-form/placeholder.tsx similarity index 100% rename from app/sections/single-product/placeholder.tsx rename to app/components/product-form/placeholder.tsx diff --git a/app/sections/single-product/product-media.tsx b/app/components/product-form/product-media.tsx similarity index 100% rename from app/sections/single-product/product-media.tsx rename to app/components/product-form/product-media.tsx diff --git a/app/sections/single-product/quantity.tsx b/app/components/product-form/quantity.tsx similarity index 100% rename from app/sections/single-product/quantity.tsx rename to app/components/product-form/quantity.tsx diff --git a/app/sections/single-product/variants.tsx b/app/components/product-form/variants.tsx similarity index 100% rename from app/sections/single-product/variants.tsx rename to app/components/product-form/variants.tsx diff --git a/app/lib/judgeme.ts b/app/lib/judgeme.ts new file mode 100644 index 0000000..9cb4460 --- /dev/null +++ b/app/lib/judgeme.ts @@ -0,0 +1,59 @@ + +type JudgemeProductData = { + product: { + id: string; + handle: string; + }; +}; + +type JudgemeReviewsData = { + reviews: { + rating: number; + }[]; +}; + +async function getInternalIdByHandle( + api_token: string, + shop_domain: string, + handle: string, +) { + let api = + `https://judge.me/api/v1/products/-1?` + + new URLSearchParams({ + api_token, + shop_domain, + handle: handle, + }); + let data = (await fetch(api).then((res) => res.json())) as JudgemeProductData; + return data?.product?.id; +} + +export let getJudgemeReviews = async(api_token: string, shop_domain: string, handle: string) => { + if (!api_token) { + return { + error: 'Missing JUDGEME_PUBLIC_TOKEN', + }; + } + let internalId = await getInternalIdByHandle(api_token, shop_domain, handle); + if (internalId) { + let data = (await fetch( + `https://judge.me/api/v1/reviews?` + + new URLSearchParams({ + api_token, + shop_domain, + product_id: internalId, + }), + ).then((res) => res.json())) as JudgemeReviewsData; + let reviews = data.reviews; + let rating = + reviews.reduce((acc, review) => acc + review.rating, 0) / + (reviews.length || 1); + return { + rating, + reviewNumber: reviews.length, + }; + } + return { + rating: 0, + }; +} \ No newline at end of file diff --git a/app/routes/($locale).api.review.$productHandle.tsx b/app/routes/($locale).api.review.$productHandle.tsx index 793bf8b..c84f1cb 100644 --- a/app/routes/($locale).api.review.$productHandle.tsx +++ b/app/routes/($locale).api.review.$productHandle.tsx @@ -1,34 +1,6 @@ import {type RouteLoaderArgs} from '@weaverse/hydrogen'; import invariant from 'tiny-invariant'; - -type JudgemeProductData = { - product: { - id: string; - handle: string; - }; -}; - -type JudgemeReviewsData = { - reviews: { - rating: number; - }[]; -}; - -async function getInternalIdByHandle( - api_token: string, - shop_domain: string, - handle: string, -) { - let api = - `https://judge.me/api/v1/products/-1?` + - new URLSearchParams({ - api_token, - shop_domain, - handle: handle, - }); - let data = (await fetch(api).then((res) => res.json())) as JudgemeProductData; - return data?.product?.id; -} +import { getJudgemeReviews } from '~/lib/judgeme'; export async function loader(args: RouteLoaderArgs) { let {params, context} = args; @@ -37,30 +9,6 @@ export async function loader(args: RouteLoaderArgs) { let api_token = env.JUDGEME_PUBLIC_TOKEN; let shop_domain = env.PUBLIC_STORE_DOMAIN; invariant(handle, 'Missing product handle'); - if (!api_token) - return { - error: 'Missing JUDGEME_PUBLIC_TOKEN', - }; - let internalId = await getInternalIdByHandle(api_token, shop_domain, handle); - if (internalId) { - let data = (await fetch( - `https://judge.me/api/v1/reviews?` + - new URLSearchParams({ - api_token, - shop_domain, - product_id: internalId, - }), - ).then((res) => res.json())) as JudgemeReviewsData; - let reviews = data.reviews; - let rating = - reviews.reduce((acc, review) => acc + review.rating, 0) / - (reviews.length || 1); - return { - rating, - reviewNumber: reviews.length, - }; - } - return { - rating: 0, - }; + let reviews = await getJudgemeReviews(api_token, shop_domain, handle); + return reviews } diff --git a/app/routes/($locale).journal.$journalHandle.tsx b/app/routes/($locale).journal.$journalHandle.tsx deleted file mode 100644 index 41311f1..0000000 --- a/app/routes/($locale).journal.$journalHandle.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import {json, type LinksFunction, type LoaderArgs} from '@shopify/remix-oxygen'; -import {useLoaderData} from '@remix-run/react'; -import {Image} from '@shopify/hydrogen'; -import invariant from 'tiny-invariant'; - -import {PageHeader, Section} from '~/components'; -import {seoPayload} from '~/lib/seo.server'; -import {routeHeaders} from '~/data/cache'; - -import styles from '../styles/custom-font.css'; - -const BLOG_HANDLE = 'journal'; - -export const headers = routeHeaders; - -export const links: LinksFunction = () => { - return [{rel: 'stylesheet', href: styles}]; -}; - -export async function loader({request, params, context}: LoaderArgs) { - const {language, country} = context.storefront.i18n; - - invariant(params.journalHandle, 'Missing journal handle'); - - const {blog} = await context.storefront.query(ARTICLE_QUERY, { - variables: { - blogHandle: BLOG_HANDLE, - articleHandle: params.journalHandle, - language, - }, - }); - - if (!blog?.articleByHandle) { - throw new Response(null, {status: 404}); - } - - const article = blog.articleByHandle; - - const formattedDate = new Intl.DateTimeFormat(`${language}-${country}`, { - year: 'numeric', - month: 'long', - day: 'numeric', - }).format(new Date(article?.publishedAt!)); - - const seo = seoPayload.article({article, url: request.url}); - - return json({article, formattedDate, seo}); -} - -export default function Article() { - const {article, formattedDate} = useLoaderData(); - - const {title, image, contentHtml, author} = article; - - return ( - <> - - - {formattedDate} · {author?.name} - - -
- {image && ( - - )} -
-
- - ); -} - -const ARTICLE_QUERY = `#graphql - query ArticleDetails( - $language: LanguageCode - $blogHandle: String! - $articleHandle: String! - ) @inContext(language: $language) { - blog(handle: $blogHandle) { - articleByHandle(handle: $articleHandle) { - title - contentHtml - publishedAt - author: authorV2 { - name - } - image { - id - altText - url - width - height - } - seo { - description - title - } - } - } - } -`; diff --git a/app/routes/($locale).journal._index.tsx b/app/routes/($locale).journal._index.tsx deleted file mode 100644 index ee238ac..0000000 --- a/app/routes/($locale).journal._index.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import {json, type LoaderArgs} from '@shopify/remix-oxygen'; -import {useLoaderData} from '@remix-run/react'; -import {flattenConnection, Image} from '@shopify/hydrogen'; - -import {Grid, PageHeader, Section, Link} from '~/components'; -import {getImageLoadingPriority, PAGINATION_SIZE} from '~/lib/const'; -import {seoPayload} from '~/lib/seo.server'; -import {routeHeaders} from '~/data/cache'; -import type {ArticleFragment} from 'storefrontapi.generated'; - -const BLOG_HANDLE = 'Journal'; - -export const headers = routeHeaders; - -export const loader = async ({request, context: {storefront}}: LoaderArgs) => { - const {language, country} = storefront.i18n; - const {blog} = await storefront.query(BLOGS_QUERY, { - variables: { - blogHandle: BLOG_HANDLE, - pageBy: PAGINATION_SIZE, - language, - }, - }); - - if (!blog?.articles) { - throw new Response('Not found', {status: 404}); - } - - const articles = flattenConnection(blog.articles).map((article) => { - const {publishedAt} = article!; - return { - ...article, - publishedAt: new Intl.DateTimeFormat(`${language}-${country}`, { - year: 'numeric', - month: 'long', - day: 'numeric', - }).format(new Date(publishedAt!)), - }; - }); - - const seo = seoPayload.blog({blog, url: request.url}); - - return json({articles, seo}); -}; - -export default function Journals() { - const {articles} = useLoaderData(); - - return ( - <> - -
- - {articles.map((article, i) => ( - - ))} - -
- - ); -} - -function ArticleCard({ - blogHandle, - article, - loading, -}: { - blogHandle: string; - article: ArticleFragment; - loading?: HTMLImageElement['loading']; -}) { - return ( -
  • - - {article.image && ( -
    - {article.image.altText -
    - )} -

    {article.title}

    - {article.publishedAt} - -
  • - ); -} - -const BLOGS_QUERY = `#graphql -query Blog( - $language: LanguageCode - $blogHandle: String! - $pageBy: Int! - $cursor: String -) @inContext(language: $language) { - blog(handle: $blogHandle) { - title - seo { - title - description - } - articles(first: $pageBy, after: $cursor) { - edges { - node { - ...Article - } - } - } - } -} - -fragment Article on Article { - author: authorV2 { - name - } - contentHtml - handle - id - image { - id - altText - url - width - height - } - publishedAt - title -} -`; diff --git a/app/routes/($locale).products.$productHandle.tsx b/app/routes/($locale).products.$productHandle.tsx index 13ad358..15ba178 100644 --- a/app/routes/($locale).products.$productHandle.tsx +++ b/app/routes/($locale).products.$productHandle.tsx @@ -1,9 +1,11 @@ +import type { + ShopifyAnalyticsProduct} from '@shopify/hydrogen'; import { AnalyticsPageType, - getSelectedProductOptions, - ShopifyAnalyticsProduct, + getSelectedProductOptions } from '@shopify/hydrogen'; -import {defer, LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import type { LoaderFunctionArgs} from '@shopify/remix-oxygen'; +import {defer} from '@shopify/remix-oxygen'; import type {ProductRecommendationsQuery} from 'storefrontapi.generated'; import invariant from 'tiny-invariant'; import {routeHeaders} from '~/data/cache'; @@ -16,8 +18,9 @@ import {seoPayload} from '~/lib/seo.server'; import type {Storefront} from '~/lib/type'; import {WeaverseContent} from '~/weaverse'; import {useLoaderData, useSearchParams} from '@remix-run/react'; -import {SelectedOptionInput} from '@shopify/hydrogen/storefront-api-types'; +import type {SelectedOptionInput} from '@shopify/hydrogen/storefront-api-types'; import {useEffect} from 'react'; +import { getJudgemeReviews } from '~/lib/judgeme'; export const headers = routeHeaders; @@ -51,7 +54,7 @@ export async function loader({params, request, context}: LoaderFunctionArgs) { // into it's own separate query that is deferred. So there's a brief moment // where variant options might show as available when they're not, but after // this deferred query resolves, the UI will update. - const variants = context.storefront.query(VARIANTS_QUERY, { + const variants = await context.storefront.query(VARIANTS_QUERY, { variables: { handle: productHandle, country: context.storefront.i18n.country, @@ -81,6 +84,13 @@ export async function loader({params, request, context}: LoaderFunctionArgs) { url: request.url, }); + let judgeme_API_TOKEN = context.env.JUDGEME_PUBLIC_TOKEN; + let judgemeReviews = null; + if (judgeme_API_TOKEN) { + let shop_domain = context.env.PUBLIC_STORE_DOMAIN; + judgemeReviews = await getJudgemeReviews(judgeme_API_TOKEN, shop_domain, productHandle); + } + return defer({ variants, product, @@ -95,6 +105,7 @@ export async function loader({params, request, context}: LoaderFunctionArgs) { }, seo, weaverseData: await context.weaverse.loadPage(), + judgemeReviews }); } diff --git a/app/sections/meta-demo.tsx b/app/sections/meta-demo.tsx index d556dd4..1d51cf1 100644 --- a/app/sections/meta-demo.tsx +++ b/app/sections/meta-demo.tsx @@ -151,7 +151,7 @@ export let schema: HydrogenComponentSchema = { label: 'Select metaobject definition', type: 'metaobject', helpText: - 'How to display this demo section', + 'How to display this demo section', name: 'metaDemo', shouldRevalidate: true, }, diff --git a/app/sections/product-information/index.tsx b/app/sections/product-information/index.tsx index 20eb494..9426cfb 100644 --- a/app/sections/product-information/index.tsx +++ b/app/sections/product-information/index.tsx @@ -1,121 +1,194 @@ -import {Await, useLoaderData} from '@remix-run/react'; -import type { - HydrogenComponentProps, - HydrogenComponentSchema, +import {useLoaderData} from '@remix-run/react'; +import {Money, ShopPayButton} from '@shopify/hydrogen'; +import { + useThemeSettings, + type HydrogenComponentProps, + type HydrogenComponentSchema, } from '@weaverse/hydrogen'; -import clsx from 'clsx'; -import {forwardRef, Suspense} from 'react'; +import {forwardRef, useEffect, useState} from 'react'; import type {ProductQuery, VariantsQuery} from 'storefrontapi.generated'; -import {Heading, ProductGallery, Section, Text} from '~/components'; +import {AddToCartButton, Text} from '~/components'; import {getExcerpt} from '~/lib/utils'; +import {ProductPlaceholder} from '../../components/product-form/placeholder'; +import {ProductMedia} from '../../components/product-form/product-media'; +import {Quantity} from '../../components/product-form/quantity'; +import {ProductVariants} from '../../components/product-form/variants'; import {ProductDetail} from './product-detail'; -import {ProductForm} from './product-form'; - -let gallerySizeMap = { - small: 'lg:col-span-2', - medium: 'lg:col-span-3', - large: 'lg:col-span-4', -}; - interface ProductInformationProps extends HydrogenComponentProps { - gallerySize: 'small' | 'medium' | 'large'; addToCartText: string; soldOutText: string; + unavailableText: string; showVendor: boolean; showSalePrice: boolean; showDetails: boolean; showShippingPolicy: boolean; showRefundPolicy: boolean; + hideUnavailableOptions: boolean; + // product media props + showThumbnails: boolean; + numberOfThumbnails: number; + spacing: number; } let ProductInformation = forwardRef( (props, ref) => { - let {product, shop, variants} = useLoaderData< + let { + product, + shop, + variants: _variants, + storeDomain, + } = useLoaderData< ProductQuery & { variants: VariantsQuery; + storeDomain: string; } >(); + let variants = _variants?.product?.variants; + let [selectedVariant, setSelectedVariant] = useState( + product?.selectedVariant, + ); let { - gallerySize, addToCartText, soldOutText, + unavailableText, showVendor, showSalePrice, showDetails, showShippingPolicy, showRefundPolicy, + hideUnavailableOptions, + showThumbnails, + numberOfThumbnails, + spacing, + children, ...rest } = props; + let [quantity, setQuantity] = useState(1); + let atcText = selectedVariant?.availableForSale + ? addToCartText + : selectedVariant?.quantityAvailable === -1 + ? unavailableText + : soldOutText; + useEffect(() => { + if (!selectedVariant) { + setSelectedVariant(variants?.nodes?.[0]); + } + }, [selectedVariant, variants?.nodes]); + let {swatches} = useThemeSettings(); + + let handleSelectedVariantChange = (variant: any) => { + setSelectedVariant(variant); + // update the url + let searchParams = new URLSearchParams(window.location.search); + for (const option of variant.selectedOptions) { + searchParams.set(option.name, option.value); + } + let url = `${window.location.pathname}?${searchParams.toString()}`; + window.history.replaceState({}, '', url); + }; + + if (!product || !selectedVariant) + return ( +
    + +
    + ); if (product && variants) { - const {media, title, vendor, descriptionHtml} = product; + const {title, vendor, descriptionHtml} = product; const {shippingPolicy, refundPolicy} = shop; return (
    -
    -
    - +
    + -
    -
    -
    - +
    +
    +
    +

    {title} - +

    {showVendor && vendor && ( {vendor} )}
    - - - {(variants) => ( - - )} - - -
    - {showDetails && descriptionHtml && ( - + {selectedVariant ? ( + - )} - {showShippingPolicy && shippingPolicy?.body && ( - - )} - {showRefundPolicy && refundPolicy?.body && ( - - )} -
    -
    + ) : null} +

    + {children} +

    + +

    + + + {atcText} + + {selectedVariant?.availableForSale && ( + + )} +
    + {showShippingPolicy && shippingPolicy?.body && ( + + )} + {showRefundPolicy && refundPolicy?.body && ( + + )} +
    -
    +
    ); } @@ -128,30 +201,12 @@ export default ProductInformation; export let schema: HydrogenComponentSchema = { type: 'product-information', title: 'Product information', + childTypes: ['judgeme'], limit: 1, enabledOn: { pages: ['PRODUCT'], }, inspector: [ - { - group: 'Product gallery', - inputs: [ - { - type: 'toggle-group', - label: 'Gallery size', - name: 'gallerySize', - configs: { - options: [ - {value: 'small', label: 'Small'}, - {value: 'medium', label: 'Medium'}, - {value: 'large', label: 'Large'}, - ], - }, - defaultValue: 'large', - helpText: 'Apply on large screens only.', - }, - ], - }, { group: 'Product form', inputs: [ @@ -169,6 +224,13 @@ export let schema: HydrogenComponentSchema = { defaultValue: 'Sold out', placeholder: 'Sold out', }, + { + type: 'text', + label: 'Unavailable text', + name: 'unavailableText', + defaultValue: 'Unavailable', + placeholder: 'Unavailable', + }, { type: 'switch', label: 'Show vendor', @@ -199,6 +261,44 @@ export let schema: HydrogenComponentSchema = { name: 'showRefundPolicy', defaultValue: true, }, + { + label: 'Hide unavailable options', + type: 'switch', + name: 'hideUnavailableOptions', + }, + ], + }, + { + group: 'Product Media', + inputs: [ + { + label: 'Show thumbnails', + name: 'showThumbnails', + type: 'switch', + defaultValue: true, + }, + { + label: 'Number of thumbnails', + name: 'numberOfThumbnails', + type: 'range', + condition: 'showThumbnails.eq.true', + configs: { + min: 1, + max: 10, + }, + defaultValue: 4, + }, + { + label: 'Gap between images', + name: 'spacing', + type: 'range', + configs: { + min: 0, + step: 2, + max: 100, + }, + defaultValue: 10, + }, ], }, ], diff --git a/app/sections/product-information/product-form.tsx b/app/sections/product-information/product-form.tsx deleted file mode 100644 index 749cb17..0000000 --- a/app/sections/product-information/product-form.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import {Listbox} from '@headlessui/react'; -import {useLoaderData} from '@remix-run/react'; -import type {ShopifyAnalyticsProduct} from '@shopify/hydrogen'; -import {Money, ShopPayButton, VariantSelector} from '@shopify/hydrogen'; -import clsx from 'clsx'; -import {useRef} from 'react'; -import type { - ProductQuery, - ProductVariantFragmentFragment, -} from 'storefrontapi.generated'; -import { - AddToCartButton, - Button, - Text, - Heading, - IconCaret, - IconCheck, - Link, -} from '~/components'; - -export function ProductForm(props: { - variants: ProductVariantFragmentFragment[]; - addToCartText: string; - soldOutText: string; - showSalePrice: boolean; -}) { - const {product, analytics, storeDomain} = useLoaderData<{ - product: NonNullable; - analytics: { - pageType: 'product'; - resourceId: any; - products: ShopifyAnalyticsProduct[]; - totalValue: number; - }; - storeDomain: string; - }>(); - const {variants, addToCartText, soldOutText, showSalePrice} = props; - - const closeRef = useRef(null); - - /** - * Likewise, we're defaulting to the first variant for purposes - * of add to cart if there is none returned from the loader. - * A developer can opt out of this, too. - */ - const selectedVariant = product.selectedVariant!; - const isOutOfStock = !selectedVariant?.availableForSale; - - const isOnSale = - selectedVariant?.price?.amount && - selectedVariant?.compareAtPrice?.amount && - selectedVariant?.price?.amount < selectedVariant?.compareAtPrice?.amount; - - const productAnalytics: ShopifyAnalyticsProduct = { - ...analytics.products[0], - quantity: 1, - }; - - return ( -
    -
    - - {({option}) => { - return ( -
    - - {option.name} - -
    - {option.values.length > 7 ? ( -
    - - {({open}) => ( - <> - - {option.value} - - - - {option.values - .filter((value) => value.isAvailable) - .map(({value, to, isActive}) => ( - - {({active}) => ( - { - if (!closeRef?.current) return; - closeRef.current.click(); - }} - > - {value} - {isActive && ( - - - - )} - - )} - - ))} - - - )} - -
    - ) : ( - option.values.map(({value, isAvailable, isActive, to}) => ( - - {value} - - )) - )} -
    -
    - ); - }} -
    - {selectedVariant && ( -
    - {isOutOfStock ? ( - - ) : ( - - - {addToCartText} ยท{' '} - {selectedVariant?.price ? ( - - ) : null} - {showSalePrice && isOnSale && ( - - )} - - - )} - {!isOutOfStock && ( - - )} -
    - )} -
    -
    - ); -} diff --git a/app/sections/single-product/index.tsx b/app/sections/single-product/index.tsx index 0e1f02f..5c9290b 100644 --- a/app/sections/single-product/index.tsx +++ b/app/sections/single-product/index.tsx @@ -9,11 +9,11 @@ import { import {forwardRef, useEffect, useState} from 'react'; import type {ProductQuery} from 'storefrontapi.generated'; import {AddToCartButton} from '~/components'; -import {PRODUCT_QUERY} from '~/data/queries'; -import {Quantity} from './quantity'; -import {ProductVariants} from './variants'; -import {ProductPlaceholder} from './placeholder'; -import {ProductMedia} from './product-media'; +import {PRODUCT_QUERY, VARIANTS_QUERY} from '~/data/queries'; +import {Quantity} from '../../components/product-form/quantity'; +import {ProductVariants} from '../../components/product-form/variants'; +import {ProductPlaceholder} from '../../components/product-form/placeholder'; +import {ProductMedia} from '../../components/product-form/product-media'; type SingleProductData = { productsCount: number; @@ -44,14 +44,14 @@ let SingleProduct = forwardRef( } = props; let {swatches} = useThemeSettings(); - let {storeDomain, product} = loaderData || {}; + let {storeDomain, product, variants: _variants} = loaderData || {}; + let variants = _variants?.product?.variants; let [selectedVariant, setSelectedVariant] = useState(null); let [quantity, setQuantity] = useState(1); useEffect(() => { - let variants = product?.variants as any; setSelectedVariant(variants?.nodes?.[0]); setQuantity(1); - }, [product]); + }, [variants?.nodes]); if (!product || !selectedVariant) return (
    @@ -101,7 +101,7 @@ let SingleProduct = forwardRef( selectedVariant={selectedVariant} onSelectedVariantChange={setSelectedVariant} swatch={swatches} - variants={product.variants} + variants={variants} options={product?.options} handle={product?.handle} hideUnavailableOptions={hideUnavailableOptions} @@ -156,17 +156,17 @@ export let loader = async (args: ComponentLoaderArgs) => { country: storefront.i18n.country, }, }); - // let variants = await storefront.query(VARIANTS_QUERY, { - // variables: { - // handle: productHandle, - // language: storefront.i18n.language, - // country: storefront.i18n.country, - // }, - // }); + let variants = await storefront.query(VARIANTS_QUERY, { + variables: { + handle: productHandle, + language: storefront.i18n.language, + country: storefront.i18n.country, + }, + }); return { product, - // variants: variants?.product?.variants, + variants, storeDomain: shop.primaryDomain.url, }; }; diff --git a/app/styles/custom-font.css b/app/styles/custom-font.css deleted file mode 100644 index e7082b9..0000000 --- a/app/styles/custom-font.css +++ /dev/null @@ -1,13 +0,0 @@ -/* @font-face { - font-family: 'IBMPlexSerif'; - font-display: swap; - font-weight: 400; - src: url('./fonts/IBMPlexSerif-Text.woff2') format('woff2'); -} -@font-face { - font-family: 'IBMPlexSerif'; - font-display: swap; - font-weight: 400; - font-style: italic; - src: url('./fonts/IBMPlexSerif-TextItalic.woff2') format('woff2'); -} */ diff --git a/app/styles/fonts/IBMPlexSerif-Text.woff2 b/app/styles/fonts/IBMPlexSerif-Text.woff2 deleted file mode 100644 index 524b8f9..0000000 Binary files a/app/styles/fonts/IBMPlexSerif-Text.woff2 and /dev/null differ diff --git a/app/styles/fonts/IBMPlexSerif-TextItalic.woff2 b/app/styles/fonts/IBMPlexSerif-TextItalic.woff2 deleted file mode 100644 index 6ab5400..0000000 Binary files a/app/styles/fonts/IBMPlexSerif-TextItalic.woff2 and /dev/null differ diff --git a/app/styles/fonts/WorkSans-Italic.woff2 b/app/styles/fonts/WorkSans-Italic.woff2 deleted file mode 100644 index 39834b0..0000000 Binary files a/app/styles/fonts/WorkSans-Italic.woff2 and /dev/null differ diff --git a/app/styles/fonts/WorkSans-Medium.woff2 b/app/styles/fonts/WorkSans-Medium.woff2 deleted file mode 100644 index 12b62a4..0000000 Binary files a/app/styles/fonts/WorkSans-Medium.woff2 and /dev/null differ diff --git a/app/styles/fonts/WorkSans.woff2 b/app/styles/fonts/WorkSans.woff2 deleted file mode 100644 index f6013f8..0000000 Binary files a/app/styles/fonts/WorkSans.woff2 and /dev/null differ diff --git a/app/weaverse/components.ts b/app/weaverse/components.ts index 638f415..ad6115e 100644 --- a/app/weaverse/components.ts +++ b/app/weaverse/components.ts @@ -29,7 +29,7 @@ import * as RelatedArticles from '~/sections/related-articles'; import * as RelatedProducts from '~/sections/related-products'; import { commonComponents } from '~/sections/shared/atoms'; import * as SingleProduct from '~/sections/single-product'; -import * as Judgeme from '~/sections/single-product/judgeme-review'; +import * as Judgeme from '~/components/product-form/judgeme-review'; import * as Testimonial from '~/sections/testimonials'; import * as TestimonialItem from '~/sections/testimonials/item'; import * as TestimonialItems from '~/sections/testimonials/items'; diff --git a/remix.config.js b/remix.config.js index 05791d2..21b3a6f 100644 --- a/remix.config.js +++ b/remix.config.js @@ -2,7 +2,13 @@ module.exports = { appDirectory: 'app', ignoredRouteFiles: ['**/.*'], - watchPaths: ['./public', './.env'], + watchPaths: [ + './public', + './.env', + '../../packages/core/dist/index.mjs', + '../../packages/react/dist/index.mjs', + '../../packages/hydrogen/dist/index.mjs', + ], server: './server.ts', /** * The following settings are required to deploy Hydrogen apps to Oxygen: