@aglyn/plugins-commerce 1.0.0-beta.237 → 1.0.0-beta.239
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/lib/components/cart.d.ts +2 -1
- package/src/lib/components/cart.js +2 -1
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/product-detail.d.ts +76 -0
- package/src/lib/components/product-detail.js +258 -52
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +35 -0
- package/src/lib/components/product-grid.js +207 -79
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/components/product-image-placeholder.d.ts +31 -0
- package/src/lib/components/product-image-placeholder.js +72 -0
- package/src/lib/components/product-image-placeholder.js.map +1 -0
- package/src/lib/components/product-reviews.d.ts +13 -0
- package/src/lib/components/product-reviews.js +267 -13
- package/src/lib/components/product-reviews.js.map +1 -1
- package/src/lib/components/related-products.d.ts +6 -0
- package/src/lib/components/related-products.js +73 -20
- package/src/lib/components/related-products.js.map +1 -1
- package/src/lib/constants/cart-events.d.ts +23 -0
- package/src/lib/constants/cart-events.js +23 -0
- package/src/lib/constants/cart-events.js.map +1 -0
- package/src/lib/server/catalog.d.ts +14 -0
- package/src/lib/server/catalog.js +25 -5
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/reviews.d.ts +35 -0
- package/src/lib/server/reviews.js +120 -1
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/site-page-resolver.d.ts +15 -3
- package/src/lib/server/site-page-resolver.js +78 -24
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/utils/add-to-cart.d.ts +35 -0
- package/src/lib/utils/add-to-cart.js +53 -0
- package/src/lib/utils/add-to-cart.js.map +1 -0
|
@@ -37,8 +37,29 @@ import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
|
|
37
37
|
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
38
38
|
import { STOREFRONT_CATALOG_DECLARATION, STOREFRONT_CATALOG_HEADERS, STOREFRONT_CATALOG_OPTIONS } from "../constants/storefront-catalog-query.js";
|
|
39
39
|
import { generatePresetId } from "../utils/generate-preset-id.js";
|
|
40
|
+
import { addToCart } from "../utils/add-to-cart.js";
|
|
41
|
+
import { ProductImagePlaceholder } from "./product-image-placeholder.js";
|
|
40
42
|
// Component ids are persisted in screen documents; never rename.
|
|
41
43
|
export const ID = 'product-grid';
|
|
44
|
+
/** What a card's quick add offers for one product. */ export function quickAddAction(item) {
|
|
45
|
+
var _item_variantCount;
|
|
46
|
+
if (item.priceComingSoon || item.soldOut) return null;
|
|
47
|
+
if (item.defaultVariantId) return 'add';
|
|
48
|
+
if (((_item_variantCount = item.variantCount) != null ? _item_variantCount : 0) > 1) return 'options';
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The chip and tag a browsing grid opens on, from the page's address:
|
|
53
|
+
* `?category=<slug>` and `?tag=<tag>`. Read after hydration only — the
|
|
54
|
+
* server renders the unfiltered first page and has no address to read.
|
|
55
|
+
*/ export function browseParamsFromSearch(search) {
|
|
56
|
+
var _params_get, _params_get1;
|
|
57
|
+
const params = new URLSearchParams(search);
|
|
58
|
+
return {
|
|
59
|
+
categorySlug: ((_params_get = params.get('category')) != null ? _params_get : '').trim(),
|
|
60
|
+
tag: ((_params_get1 = params.get('tag')) != null ? _params_get1 : '').trim()
|
|
61
|
+
};
|
|
62
|
+
}
|
|
42
63
|
/** What a card says where a product listed before it has a price would show one (AGL-3676). */ const PRICE_COMING_SOON_LABEL = 'Price coming soon';
|
|
43
64
|
const SAMPLE_ITEMS = [
|
|
44
65
|
{
|
|
@@ -141,7 +162,7 @@ function priceLabel(item) {
|
|
|
141
162
|
*/ const ProductGrid = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
142
163
|
var _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
|
|
143
164
|
var _site_pageData_commerce_grids, _site_pageData_commerce, _site_pageData;
|
|
144
|
-
const { source, collectionId, collectionSlug: collectionSlugProp, categoryId, categorySlug, tag, sort: sortProp, columns, maxItems, emptyText, emptyTitle, emptyActionLabel, emptyActionHref, showFilters, showSearch, showCategories, showSort, showTypeFilter, showPriceFilter, pageSize, cardStyle } = props, rest = _object_without_properties_loose(props, [
|
|
165
|
+
const { source, collectionId, collectionSlug: collectionSlugProp, categoryId, categorySlug, tag, sort: sortProp, columns, maxItems, emptyText, emptyTitle, emptyActionLabel, emptyActionHref, showFilters, showSearch, showCategories, showSort, showTypeFilter, showPriceFilter, pageSize, cardStyle, quickAdd } = props, rest = _object_without_properties_loose(props, [
|
|
145
166
|
"source",
|
|
146
167
|
"collectionId",
|
|
147
168
|
"collectionSlug",
|
|
@@ -162,7 +183,8 @@ function priceLabel(item) {
|
|
|
162
183
|
"showTypeFilter",
|
|
163
184
|
"showPriceFilter",
|
|
164
185
|
"pageSize",
|
|
165
|
-
"cardStyle"
|
|
186
|
+
"cardStyle",
|
|
187
|
+
"quickAdd"
|
|
166
188
|
]);
|
|
167
189
|
const photoCards = cardStyle === 'photo';
|
|
168
190
|
// Node styles ride the renderer-merged sx; recompose (stack.ts pattern).
|
|
@@ -170,7 +192,11 @@ function priceLabel(item) {
|
|
|
170
192
|
props['sx']
|
|
171
193
|
];
|
|
172
194
|
const site = Aglyn.useSite();
|
|
195
|
+
const siteFetch = Aglyn.useSiteFetch();
|
|
173
196
|
const { hostId } = site;
|
|
197
|
+
// Quick add (AGL-3676 follow-up): which card is adding, which just did.
|
|
198
|
+
const [quickAdding, setQuickAdding] = useState('');
|
|
199
|
+
const [quickAdded, setQuickAdded] = useState('');
|
|
174
200
|
// Server-rendered first page (AGL-659). The site-page enricher ran this
|
|
175
201
|
// grid's own query on the server and keyed the result by node id — two
|
|
176
202
|
// grids on a page have different queries, so an unkeyed seed would put
|
|
@@ -208,6 +234,29 @@ function priceLabel(item) {
|
|
|
208
234
|
const collectionSlug = source === 'collection' ? collectionSlugProp || collectionSlugFromLocation() : collectionSlugProp;
|
|
209
235
|
const pageLimit = pageSize && Number(pageSize) > 0 ? Math.floor(Number(pageSize)) : 0;
|
|
210
236
|
const pinnedCategoryId = source === 'category' ? categoryId != null ? categoryId : '' : '';
|
|
237
|
+
// A browsing grid opens on the address's `?category=` / `?tag=`: a shop
|
|
238
|
+
// page linked as /shop?category=candles shows that chip picked. Read in
|
|
239
|
+
// an effect, so the server and the first client render agree.
|
|
240
|
+
const [urlCategorySlug, setUrlCategorySlug] = useState('');
|
|
241
|
+
useEffect(()=>{
|
|
242
|
+
if (!hostId || typeof window === 'undefined') return;
|
|
243
|
+
const asked = browseParamsFromSearch(window.location.search);
|
|
244
|
+
if (showCategories && asked.categorySlug) setUrlCategorySlug(asked.categorySlug);
|
|
245
|
+
if (showFilters && asked.tag) setActiveTag(asked.tag);
|
|
246
|
+
// Once, on arrival: later chip picks are the visitor's.
|
|
247
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
248
|
+
}, [
|
|
249
|
+
hostId
|
|
250
|
+
]);
|
|
251
|
+
useEffect(()=>{
|
|
252
|
+
if (!urlCategorySlug || !categories.length) return;
|
|
253
|
+
const match = categories.find((category)=>category.slug === urlCategorySlug);
|
|
254
|
+
if (match) setActiveCategoryId(match.id);
|
|
255
|
+
setUrlCategorySlug('');
|
|
256
|
+
}, [
|
|
257
|
+
urlCategorySlug,
|
|
258
|
+
categories
|
|
259
|
+
]);
|
|
211
260
|
// Debounce keystrokes so large catalogs aren't fetched per letter.
|
|
212
261
|
useEffect(()=>{
|
|
213
262
|
const timer = setTimeout(()=>setQuery(searchInput.trim()), 300);
|
|
@@ -663,90 +712,118 @@ function priceLabel(item) {
|
|
|
663
712
|
const imageUrl = Aglyn.siteRelativeMediaSrc(item.imageUrl, {
|
|
664
713
|
hostId
|
|
665
714
|
});
|
|
666
|
-
return /*#__PURE__*/
|
|
715
|
+
return /*#__PURE__*/ _jsxs(Card, {
|
|
667
716
|
variant: photoCards ? 'elevation' : 'outlined',
|
|
668
717
|
elevation: 0,
|
|
669
718
|
sx: photoCards ? {
|
|
670
719
|
bgcolor: 'transparent',
|
|
671
720
|
overflow: 'visible'
|
|
672
721
|
} : undefined,
|
|
673
|
-
children:
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
722
|
+
children: [
|
|
723
|
+
/*#__PURE__*/ _jsxs(CardActionArea, {
|
|
724
|
+
href: hostId ? `/products/${item.slug}` : undefined,
|
|
725
|
+
disabled: !hostId,
|
|
726
|
+
sx: photoCards ? {
|
|
727
|
+
borderRadius: 2
|
|
728
|
+
} : undefined,
|
|
729
|
+
children: [
|
|
730
|
+
imageUrl ? /*#__PURE__*/ _jsx(CardMedia, _extends({
|
|
731
|
+
component: "img",
|
|
732
|
+
image: imageUrl,
|
|
733
|
+
alt: item.name,
|
|
734
|
+
sx: photoCards ? {
|
|
735
|
+
aspectRatio: '4 / 5',
|
|
736
|
+
objectFit: 'cover',
|
|
737
|
+
borderRadius: 2,
|
|
738
|
+
// A storefront's photo answers the pointer (AGL-3676).
|
|
739
|
+
transition: 'opacity 200ms ease',
|
|
740
|
+
'.MuiCardActionArea-root:hover &': {
|
|
741
|
+
opacity: 0.88
|
|
742
|
+
}
|
|
743
|
+
} : {
|
|
744
|
+
height: 160,
|
|
745
|
+
objectFit: 'cover'
|
|
692
746
|
}
|
|
693
|
-
} :
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
747
|
+
}, Aglyn.DEFERRED_IMAGE_ATTRIBUTES)) : // Never a blank tile (AGL-3676 follow-up).
|
|
748
|
+
/*#__PURE__*/ _jsx(ProductImagePlaceholder, {
|
|
749
|
+
name: item.name,
|
|
750
|
+
sx: photoCards ? {
|
|
751
|
+
aspectRatio: '4 / 5',
|
|
752
|
+
borderRadius: 2
|
|
753
|
+
} : {
|
|
754
|
+
height: 160
|
|
755
|
+
}
|
|
756
|
+
}),
|
|
757
|
+
/*#__PURE__*/ _jsxs(CardContent, {
|
|
758
|
+
sx: photoCards ? {
|
|
759
|
+
px: 0.5,
|
|
760
|
+
pt: 1.5,
|
|
761
|
+
pb: 1
|
|
762
|
+
} : undefined,
|
|
763
|
+
children: [
|
|
764
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
765
|
+
variant: photoCards ? 'subtitle1' : 'subtitle2',
|
|
766
|
+
noWrap: true,
|
|
767
|
+
children: item.name
|
|
768
|
+
}),
|
|
769
|
+
/*#__PURE__*/ _jsxs(Box, {
|
|
770
|
+
sx: {
|
|
771
|
+
display: 'flex',
|
|
772
|
+
gap: 1,
|
|
773
|
+
alignItems: 'center'
|
|
774
|
+
},
|
|
775
|
+
children: [
|
|
776
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
777
|
+
variant: "body2",
|
|
778
|
+
color: "text.secondary",
|
|
779
|
+
children: priceLabel(item)
|
|
780
|
+
}),
|
|
781
|
+
item.compareAtPriceUsd ? /*#__PURE__*/ _jsx(Typography, {
|
|
782
|
+
variant: "caption",
|
|
783
|
+
color: "text.disabled",
|
|
784
|
+
sx: {
|
|
785
|
+
textDecoration: 'line-through'
|
|
786
|
+
},
|
|
787
|
+
children: `$${item.compareAtPriceUsd}`
|
|
788
|
+
}) : null,
|
|
789
|
+
item.soldOut ? /*#__PURE__*/ _jsx(Chip, {
|
|
790
|
+
label: "Sold out",
|
|
791
|
+
size: "small",
|
|
792
|
+
variant: "outlined"
|
|
793
|
+
}) : null
|
|
794
|
+
]
|
|
795
|
+
})
|
|
796
|
+
]
|
|
797
|
+
})
|
|
798
|
+
]
|
|
799
|
+
}),
|
|
800
|
+
quickAdd ? /*#__PURE__*/ _jsx(QuickAddButton, {
|
|
801
|
+
item: item,
|
|
802
|
+
inert: !hostId,
|
|
803
|
+
busy: quickAdding === item.id,
|
|
804
|
+
added: quickAdded === item.id,
|
|
805
|
+
photoCards: photoCards,
|
|
806
|
+
onAdd: async ()=>{
|
|
807
|
+
if (!hostId || !item.defaultVariantId || quickAdding) return;
|
|
808
|
+
setQuickAdding(item.id);
|
|
809
|
+
const ok = await addToCart({
|
|
810
|
+
hostId,
|
|
811
|
+
siteFetch,
|
|
812
|
+
productId: item.id,
|
|
813
|
+
variantId: item.defaultVariantId,
|
|
814
|
+
quantity: 1,
|
|
815
|
+
item: {
|
|
816
|
+
item_id: item.id,
|
|
817
|
+
item_name: item.name,
|
|
818
|
+
price: item.priceUsd,
|
|
819
|
+
quantity: 1
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
setQuickAdding('');
|
|
823
|
+
if (ok) setQuickAdded(item.id);
|
|
824
|
+
}
|
|
825
|
+
}) : null
|
|
826
|
+
]
|
|
750
827
|
}, item.id);
|
|
751
828
|
})
|
|
752
829
|
});
|
|
@@ -834,6 +911,50 @@ function priceLabel(item) {
|
|
|
834
911
|
]
|
|
835
912
|
}));
|
|
836
913
|
});
|
|
914
|
+
/**
|
|
915
|
+
* A card's quick add: outside the card's link, so a click never follows it.
|
|
916
|
+
* Sample cards on the canvas show the button inert.
|
|
917
|
+
*/ function QuickAddButton({ item, inert, busy, added, photoCards, onAdd }) {
|
|
918
|
+
const action = inert ? 'add' : quickAddAction(item);
|
|
919
|
+
if (!action) return null;
|
|
920
|
+
const sx = photoCards ? {
|
|
921
|
+
mt: 0.5,
|
|
922
|
+
mx: 0.5,
|
|
923
|
+
mb: 1
|
|
924
|
+
} : {
|
|
925
|
+
mx: 2,
|
|
926
|
+
mb: 2
|
|
927
|
+
};
|
|
928
|
+
if (action === 'options') {
|
|
929
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
930
|
+
sx: sx,
|
|
931
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
932
|
+
size: "small",
|
|
933
|
+
variant: "outlined",
|
|
934
|
+
fullWidth: true,
|
|
935
|
+
href: `/products/${item.slug}`,
|
|
936
|
+
onClick: (event)=>event.stopPropagation(),
|
|
937
|
+
children: 'Choose options'
|
|
938
|
+
})
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
942
|
+
sx: sx,
|
|
943
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
944
|
+
size: "small",
|
|
945
|
+
variant: "outlined",
|
|
946
|
+
fullWidth: true,
|
|
947
|
+
disabled: inert || busy,
|
|
948
|
+
"aria-label": `Add ${item.name} to cart`,
|
|
949
|
+
onClick: (event)=>{
|
|
950
|
+
event.preventDefault();
|
|
951
|
+
event.stopPropagation();
|
|
952
|
+
onAdd();
|
|
953
|
+
},
|
|
954
|
+
children: added ? 'Added ✓' : busy ? 'Adding…' : 'Add to cart'
|
|
955
|
+
})
|
|
956
|
+
});
|
|
957
|
+
}
|
|
837
958
|
ProductGrid.displayName = 'AglynProductGrid';
|
|
838
959
|
export const schema = {
|
|
839
960
|
$id: ID,
|
|
@@ -1025,6 +1146,13 @@ export const schema = {
|
|
|
1025
1146
|
value: 'photo'
|
|
1026
1147
|
}
|
|
1027
1148
|
]
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
// Off by default, so no existing grid changes shape on deploy.
|
|
1152
|
+
name: 'quickAdd',
|
|
1153
|
+
label: 'Quick add to cart',
|
|
1154
|
+
description: 'An Add to cart button on each card for a product with one variant; ' + '"Choose options" for one with several.',
|
|
1155
|
+
component: Aglyn.FieldComponentType.CHECKBOX
|
|
1028
1156
|
}
|
|
1029
1157
|
]
|
|
1030
1158
|
};
|