+
-
-
-
-
+
+
+
+
{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: