@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
|
@@ -18,18 +18,21 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
18
18
|
* limitations under the License.
|
|
19
19
|
*/ import { advertisingConsentField } from "@aglyn/aglyn/app-utils/advertising-consent";
|
|
20
20
|
import * as Aglyn from "@aglyn/aglyn";
|
|
21
|
-
import {
|
|
22
|
-
import { recordSiteJourneyStep } from "@aglyn/aglyn/app-utils/site-journey";
|
|
21
|
+
import { buildBeginCheckoutParams, trackEvent, trackEventBeforeNavigation } from "@aglyn/aglyn/app-utils/analytics-events";
|
|
23
22
|
import { isPaymentsNotConfigured } from "@aglyn/aglyn/app-utils/payments-configured";
|
|
24
23
|
import { storefrontPaymentsNotConfiguredText } from "../constants/storefront-payments.js";
|
|
25
24
|
import * as CommerceModel from "../model/index.js";
|
|
26
|
-
import { mdiTagOutline } from "@aglyn/shared-data-mdi";
|
|
25
|
+
import { mdiChevronDown, mdiTagOutline } from "@aglyn/shared-data-mdi";
|
|
26
|
+
import Accordion from "@mui/material/Accordion";
|
|
27
|
+
import AccordionDetails from "@mui/material/AccordionDetails";
|
|
28
|
+
import AccordionSummary from "@mui/material/AccordionSummary";
|
|
27
29
|
import Alert from "@mui/material/Alert";
|
|
28
30
|
import Box from "@mui/material/Box";
|
|
29
31
|
import Button from "@mui/material/Button";
|
|
30
32
|
import Chip from "@mui/material/Chip";
|
|
31
33
|
import MenuItem from "@mui/material/MenuItem";
|
|
32
34
|
import Skeleton from "@mui/material/Skeleton";
|
|
35
|
+
import SvgIcon from "@mui/material/SvgIcon";
|
|
33
36
|
import TextField from "@mui/material/TextField";
|
|
34
37
|
import ToggleButton from "@mui/material/ToggleButton";
|
|
35
38
|
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
|
|
@@ -38,7 +41,7 @@ import { Suspense, forwardRef, lazy, useEffect, useMemo, useRef, useState } from
|
|
|
38
41
|
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
39
42
|
import { generatePresetId } from "../utils/generate-preset-id.js";
|
|
40
43
|
import { useStorefrontPurchaseEvent } from "../utils/use-storefront-purchase-event.js";
|
|
41
|
-
import {
|
|
44
|
+
import { addToCart } from "../utils/add-to-cart.js";
|
|
42
45
|
import { CartFulfillmentChoice, useCartFulfillment } from "./cart-fulfillment.js";
|
|
43
46
|
import { ID as PRODUCT_REVIEWS_ID } from "./product-reviews.js";
|
|
44
47
|
import { ID as RELATED_PRODUCTS_ID } from "./related-products.js";
|
|
@@ -120,6 +123,84 @@ const SAMPLE = {
|
|
|
120
123
|
quantity
|
|
121
124
|
};
|
|
122
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Which options are a CHOICE (AGL-3676 follow-up). A select with one entry —
|
|
128
|
+
* or with entries no variant carries — is not a choice, and on a live store
|
|
129
|
+
* it read as a broken control: an AI-built candle listed a "Scent" select
|
|
130
|
+
* that offered nothing to pick. Decided from the variants, which are what is
|
|
131
|
+
* sold: an option's values count only where a variant carries them, and only
|
|
132
|
+
* when no variant names the option at all do its listed values stand in —
|
|
133
|
+
* and then as a choice only when there are several variants to choose among.
|
|
134
|
+
*/ export function optionPresentation(options, variants) {
|
|
135
|
+
return options.map((option)=>{
|
|
136
|
+
var _option_values;
|
|
137
|
+
const listed = [
|
|
138
|
+
...new Set(((_option_values = option.values) != null ? _option_values : []).map((value)=>String(value).trim()).filter(Boolean))
|
|
139
|
+
];
|
|
140
|
+
const carried = new Set(variants.map((variant)=>{
|
|
141
|
+
var _variant_options;
|
|
142
|
+
return (_variant_options = variant.options) == null ? void 0 : _variant_options[option.name];
|
|
143
|
+
}).filter((value)=>typeof value === 'string' && value.trim() !== ''));
|
|
144
|
+
const values = carried.size ? [
|
|
145
|
+
...listed.filter((value)=>carried.has(value)),
|
|
146
|
+
...[
|
|
147
|
+
...carried
|
|
148
|
+
].filter((value)=>!listed.includes(value))
|
|
149
|
+
] : variants.length > 1 || listed.length === 1 ? listed : [];
|
|
150
|
+
return {
|
|
151
|
+
name: option.name,
|
|
152
|
+
values,
|
|
153
|
+
mode: values.length >= 2 ? 'select' : values.length === 1 ? 'text' : 'hidden'
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** The selections a page opens on: the first variant on sale, else the first. */ function initialSelections(product) {
|
|
158
|
+
var _ref, _ref1;
|
|
159
|
+
const first = (_ref = product == null ? void 0 : product.variants.find((variant)=>!variant.soldOut)) != null ? _ref : product == null ? void 0 : product.variants[0];
|
|
160
|
+
return _extends({}, (_ref1 = first == null ? void 0 : first.options) != null ? _ref1 : {});
|
|
161
|
+
}
|
|
162
|
+
/** What the Details panel lists: only facts the product record holds. */ export function productDetailFacts(product, variant, presented) {
|
|
163
|
+
var _product_tags;
|
|
164
|
+
const facts = [];
|
|
165
|
+
if (product.type === 'digital') facts.push({
|
|
166
|
+
label: 'Format',
|
|
167
|
+
value: 'Digital product'
|
|
168
|
+
});
|
|
169
|
+
if (product.type === 'service') facts.push({
|
|
170
|
+
label: 'Format',
|
|
171
|
+
value: 'Service'
|
|
172
|
+
});
|
|
173
|
+
for (const option of presented){
|
|
174
|
+
if (option.mode !== 'hidden') {
|
|
175
|
+
facts.push({
|
|
176
|
+
label: option.name,
|
|
177
|
+
value: option.values.join(', ')
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (variant == null ? void 0 : variant.sku) facts.push({
|
|
182
|
+
label: 'SKU',
|
|
183
|
+
value: variant.sku
|
|
184
|
+
});
|
|
185
|
+
if ((_product_tags = product.tags) == null ? void 0 : _product_tags.length) facts.push({
|
|
186
|
+
label: 'Tags',
|
|
187
|
+
value: product.tags.join(', ')
|
|
188
|
+
});
|
|
189
|
+
return facts;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* What the Shipping & returns panel says, from the product's kind and the
|
|
193
|
+
* store's own return settings — never a policy the store has not set.
|
|
194
|
+
*/ export function productShippingLines(product, returns) {
|
|
195
|
+
const lines = [];
|
|
196
|
+
if (product.type === 'digital') lines.push('Delivered digitally after purchase — nothing ships.');
|
|
197
|
+
else if (product.type === 'service') lines.push('A service — nothing ships.');
|
|
198
|
+
else lines.push('Shipping is calculated at checkout.');
|
|
199
|
+
if (returns && returns.windowDays > 0) {
|
|
200
|
+
lines.push(`Returns accepted within ${returns.windowDays} ` + `${returns.windowDays === 1 ? 'day' : 'days'} of shipping — ` + 'request one from your order status page.');
|
|
201
|
+
}
|
|
202
|
+
return lines;
|
|
203
|
+
}
|
|
123
204
|
function slugFromLocation() {
|
|
124
205
|
if (typeof window === 'undefined') return '';
|
|
125
206
|
const match = window.location.pathname.match(/\/products\/([^/?#]+)/);
|
|
@@ -133,12 +214,14 @@ function slugFromLocation() {
|
|
|
133
214
|
* slug prop pins it; sample data on the inert canvas.
|
|
134
215
|
*/ const ProductDetail = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
135
216
|
var _ref, _ref1, _ref2;
|
|
136
|
-
var
|
|
137
|
-
const { slug: slugProp, buyLabel, hideDescription, showCoupon } = props, rest = _object_without_properties_loose(props, [
|
|
217
|
+
var _site_pageData;
|
|
218
|
+
const { slug: slugProp, buyLabel, hideDescription, showCoupon, showDetails, showShipping } = props, rest = _object_without_properties_loose(props, [
|
|
138
219
|
"slug",
|
|
139
220
|
"buyLabel",
|
|
140
221
|
"hideDescription",
|
|
141
|
-
"showCoupon"
|
|
222
|
+
"showCoupon",
|
|
223
|
+
"showDetails",
|
|
224
|
+
"showShipping"
|
|
142
225
|
]);
|
|
143
226
|
// Node styles ride the renderer-merged sx; recompose (stack.ts pattern).
|
|
144
227
|
const nodeSx = Array.isArray(props['sx']) ? props['sx'] : [
|
|
@@ -157,12 +240,19 @@ function slugFromLocation() {
|
|
|
157
240
|
// emitted a <Skeleton> and the crawler got a product page with no
|
|
158
241
|
// product in it. Absent in the besigner/preview, where the effect below
|
|
159
242
|
// still fetches — so this is an optimisation, not a new requirement.
|
|
160
|
-
const
|
|
243
|
+
const seededCommerce = (_site_pageData = site.pageData) == null ? void 0 : _site_pageData.commerce;
|
|
244
|
+
const seededProduct = seededCommerce == null ? void 0 : seededCommerce.product;
|
|
245
|
+
// The store's return window, seeded with the built-in product page only
|
|
246
|
+
// when the store takes returns of this product's kind.
|
|
247
|
+
const seededReturns = seededCommerce == null ? void 0 : seededCommerce.returns;
|
|
161
248
|
const [detail, setDetail] = useState(seededProduct != null ? seededProduct : null);
|
|
162
|
-
|
|
249
|
+
// Opened on the first variant on sale. A seeded page skips the fetch that
|
|
250
|
+
// used to set this, so it opened with nothing selected — an empty select.
|
|
251
|
+
const [selections, setSelections] = useState(()=>initialSelections(seededProduct));
|
|
163
252
|
const [quantity, setQuantity] = useState(1);
|
|
164
253
|
/** A discount or coupon code typed on the product page. */ const [coupon, setCoupon] = useState('');
|
|
165
|
-
|
|
254
|
+
// A thumbnail the shopper picked; null follows the variant's own photo.
|
|
255
|
+
const [activeImage, setActiveImage] = useState(null);
|
|
166
256
|
// `unconfigured` is not an `error` with softer words (AGL-2019). A store
|
|
167
257
|
// with no Stripe key answers 501, and rendering that at `severity="error"`
|
|
168
258
|
// tells a shopper their payment failed when nothing was ever attempted —
|
|
@@ -215,7 +305,6 @@ function slugFromLocation() {
|
|
|
215
305
|
const product = payload == null ? void 0 : payload.product;
|
|
216
306
|
setDetail(product != null ? product : 'missing');
|
|
217
307
|
if (product) {
|
|
218
|
-
var _product_variants_find, _ref;
|
|
219
308
|
// GA4 ecommerce mirror (AGL-327), through the shared taxonomy
|
|
220
309
|
// since AGL-1591 rather than raw gtag — same consent gate, same
|
|
221
310
|
// sanitizer, and the name is now checked by the compiler.
|
|
@@ -227,8 +316,7 @@ function slugFromLocation() {
|
|
|
227
316
|
}
|
|
228
317
|
]
|
|
229
318
|
});
|
|
230
|
-
|
|
231
|
-
setSelections((_ref = first == null ? void 0 : first.options) != null ? _ref : {});
|
|
319
|
+
setSelections(initialSelections(product));
|
|
232
320
|
}
|
|
233
321
|
}).catch(()=>{
|
|
234
322
|
if (active) setDetail('missing');
|
|
@@ -256,36 +344,20 @@ function slugFromLocation() {
|
|
|
256
344
|
const handleAddToCart = async ()=>{
|
|
257
345
|
if (!hostId || !resolved || !variant) return;
|
|
258
346
|
setAdded(false);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
})
|
|
272
|
-
if (
|
|
273
|
-
setAdded(true);
|
|
274
|
-
// A funnel step (AGL-3605): the product, by its id.
|
|
275
|
-
recordSiteJourneyStep('cart', resolved.id);
|
|
276
|
-
// Priced (AGL-1591's shape, completed): the resolved variant's price
|
|
277
|
-
// and the chosen quantity are both known here and both come from the
|
|
278
|
-
// server's product payload, so GA4's "value added to cart" is a real
|
|
279
|
-
// number rather than the empty column an items-only hit leaves. The
|
|
280
|
-
// `value` describes what was JUST ADDED, not the cart's new total —
|
|
281
|
-
// see `buildAddToCartParams`.
|
|
282
|
-
trackEvent('add_to_cart', buildAddToCartParams({
|
|
283
|
-
items: [
|
|
284
|
-
buyItem(resolved, variant, quantity)
|
|
285
|
-
]
|
|
286
|
-
}));
|
|
287
|
-
window.dispatchEvent(new Event(CART_UPDATED_EVENT));
|
|
288
|
-
}
|
|
347
|
+
// Priced (AGL-1591's shape, completed): the resolved variant's price
|
|
348
|
+
// and the chosen quantity both come from the server's product payload,
|
|
349
|
+
// so GA4's "value added to cart" is a real number. The funnel step
|
|
350
|
+
// (AGL-3605) and the badge refresh ride the same shared helper the
|
|
351
|
+
// product grid's quick add uses.
|
|
352
|
+
const ok = await addToCart({
|
|
353
|
+
hostId,
|
|
354
|
+
siteFetch,
|
|
355
|
+
productId: resolved.id,
|
|
356
|
+
variantId: variant.id,
|
|
357
|
+
quantity,
|
|
358
|
+
item: buyItem(resolved, variant, quantity)
|
|
359
|
+
});
|
|
360
|
+
if (ok) setAdded(true);
|
|
289
361
|
};
|
|
290
362
|
/**
|
|
291
363
|
* Idempotency key for ONE buy-now attempt (AGL-1697).
|
|
@@ -501,9 +573,12 @@ function slugFromLocation() {
|
|
|
501
573
|
// schema.org `image` array below is read out of band by a crawler, where
|
|
502
574
|
// an absolute URL is the point. That one wants `absoluteMediaSrc` with
|
|
503
575
|
// the site's own origin, which is the tenant page's job (AGL-1725).
|
|
504
|
-
const galleryImage = Aglyn.siteRelativeMediaSrc((_ref1 = (_ref2 =
|
|
576
|
+
const galleryImage = Aglyn.siteRelativeMediaSrc((_ref1 = (_ref2 = activeImage !== null ? resolved.mediaUrls[activeImage] : undefined) != null ? _ref2 : variant == null ? void 0 : variant.imageUrl) != null ? _ref1 : resolved.mediaUrls[0], {
|
|
505
577
|
hostId
|
|
506
578
|
});
|
|
579
|
+
const presentedOptions = optionPresentation(resolved.options, resolved.variants);
|
|
580
|
+
const detailFacts = showDetails ? productDetailFacts(resolved, variant, presentedOptions) : [];
|
|
581
|
+
const shippingLines = showShipping ? productShippingLines(resolved, (seededProduct == null ? void 0 : seededProduct.id) === resolved.id ? seededReturns : undefined) : [];
|
|
507
582
|
// Subscription framing (AGL-545): subscription-only products price as
|
|
508
583
|
// $X/mo|/yr with a "Subscribe" button; subscriptionOptional products
|
|
509
584
|
// surface the one-time/subscribe toggle instead. Same price either way
|
|
@@ -608,13 +683,13 @@ function slugFromLocation() {
|
|
|
608
683
|
alt: "",
|
|
609
684
|
onClick: ()=>setActiveImage(index),
|
|
610
685
|
sx: {
|
|
611
|
-
width:
|
|
612
|
-
height:
|
|
686
|
+
width: 64,
|
|
687
|
+
height: 64,
|
|
613
688
|
objectFit: 'cover',
|
|
614
689
|
borderRadius: 1,
|
|
615
690
|
cursor: 'pointer',
|
|
616
691
|
border: 2,
|
|
617
|
-
borderColor: index === activeImage ? 'primary.main' : 'transparent'
|
|
692
|
+
borderColor: index === (activeImage != null ? activeImage : 0) ? 'primary.main' : 'transparent'
|
|
618
693
|
}
|
|
619
694
|
}, Aglyn.DEFERRED_IMAGE_ATTRIBUTES), `${url}-${index}`))
|
|
620
695
|
}) : null
|
|
@@ -711,14 +786,25 @@ function slugFromLocation() {
|
|
|
711
786
|
})
|
|
712
787
|
]
|
|
713
788
|
}) : null,
|
|
714
|
-
|
|
789
|
+
presentedOptions.map((option)=>{
|
|
715
790
|
var _selections_option_name;
|
|
716
|
-
return /*#__PURE__*/ _jsx(
|
|
791
|
+
return option.mode === 'hidden' ? null : option.mode === 'text' ? /*#__PURE__*/ _jsx(Typography, {
|
|
792
|
+
variant: "body2",
|
|
793
|
+
color: "text.secondary",
|
|
794
|
+
sx: {
|
|
795
|
+
mb: 1.5
|
|
796
|
+
},
|
|
797
|
+
children: `${option.name}: ${option.values[0]}`
|
|
798
|
+
}, option.name) : /*#__PURE__*/ _jsx(TextField, {
|
|
717
799
|
label: option.name,
|
|
718
|
-
value: (_selections_option_name = selections[option.name]) != null ? _selections_option_name : '',
|
|
719
|
-
onChange: (event)=>
|
|
800
|
+
value: option.values.includes((_selections_option_name = selections[option.name]) != null ? _selections_option_name : '') ? selections[option.name] : '',
|
|
801
|
+
onChange: (event)=>{
|
|
802
|
+
// A new variant shows its own photo, not the last thumbnail.
|
|
803
|
+
setActiveImage(null);
|
|
804
|
+
setSelections((prev)=>_extends({}, prev, {
|
|
720
805
|
[option.name]: event.target.value
|
|
721
|
-
}))
|
|
806
|
+
}));
|
|
807
|
+
},
|
|
722
808
|
size: "small",
|
|
723
809
|
select: true,
|
|
724
810
|
fullWidth: true,
|
|
@@ -937,7 +1023,65 @@ function slugFromLocation() {
|
|
|
937
1023
|
!hideDescription && resolved.description ? /*#__PURE__*/ _jsx(Typography, {
|
|
938
1024
|
variant: "body1",
|
|
939
1025
|
color: "text.secondary",
|
|
1026
|
+
sx: {
|
|
1027
|
+
whiteSpace: 'pre-line'
|
|
1028
|
+
},
|
|
940
1029
|
children: resolved.description
|
|
1030
|
+
}) : null,
|
|
1031
|
+
detailFacts.length || shippingLines.length ? /*#__PURE__*/ _jsxs(Box, {
|
|
1032
|
+
sx: {
|
|
1033
|
+
mt: 3
|
|
1034
|
+
},
|
|
1035
|
+
children: [
|
|
1036
|
+
detailFacts.length ? /*#__PURE__*/ _jsx(ProductDetailPanel, {
|
|
1037
|
+
title: "Details",
|
|
1038
|
+
children: /*#__PURE__*/ _jsx(Box, {
|
|
1039
|
+
component: "dl",
|
|
1040
|
+
sx: {
|
|
1041
|
+
m: 0,
|
|
1042
|
+
display: 'grid',
|
|
1043
|
+
gridTemplateColumns: 'auto 1fr',
|
|
1044
|
+
columnGap: 2,
|
|
1045
|
+
rowGap: 0.75
|
|
1046
|
+
},
|
|
1047
|
+
children: detailFacts.map((fact)=>/*#__PURE__*/ _jsxs(Box, {
|
|
1048
|
+
sx: {
|
|
1049
|
+
display: 'contents'
|
|
1050
|
+
},
|
|
1051
|
+
children: [
|
|
1052
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1053
|
+
component: "dt",
|
|
1054
|
+
variant: "body2",
|
|
1055
|
+
sx: {
|
|
1056
|
+
fontWeight: 600
|
|
1057
|
+
},
|
|
1058
|
+
children: fact.label
|
|
1059
|
+
}),
|
|
1060
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1061
|
+
component: "dd",
|
|
1062
|
+
variant: "body2",
|
|
1063
|
+
color: "text.secondary",
|
|
1064
|
+
sx: {
|
|
1065
|
+
m: 0
|
|
1066
|
+
},
|
|
1067
|
+
children: fact.value
|
|
1068
|
+
})
|
|
1069
|
+
]
|
|
1070
|
+
}, fact.label))
|
|
1071
|
+
})
|
|
1072
|
+
}) : null,
|
|
1073
|
+
shippingLines.length ? /*#__PURE__*/ _jsx(ProductDetailPanel, {
|
|
1074
|
+
title: "Shipping & returns",
|
|
1075
|
+
children: shippingLines.map((line)=>/*#__PURE__*/ _jsx(Typography, {
|
|
1076
|
+
variant: "body2",
|
|
1077
|
+
color: "text.secondary",
|
|
1078
|
+
sx: {
|
|
1079
|
+
mb: 0.75
|
|
1080
|
+
},
|
|
1081
|
+
children: line
|
|
1082
|
+
}, line))
|
|
1083
|
+
}) : null
|
|
1084
|
+
]
|
|
941
1085
|
}) : null
|
|
942
1086
|
]
|
|
943
1087
|
})
|
|
@@ -945,6 +1089,55 @@ function slugFromLocation() {
|
|
|
945
1089
|
}));
|
|
946
1090
|
});
|
|
947
1091
|
ProductDetail.displayName = 'AglynProductDetail';
|
|
1092
|
+
/**
|
|
1093
|
+
* One fold of the panels under the buy box: a flat, divided accordion in the
|
|
1094
|
+
* theme's own colours — the way a designer storefront stacks Details and
|
|
1095
|
+
* Shipping under the description.
|
|
1096
|
+
*/ function ProductDetailPanel({ title, children }) {
|
|
1097
|
+
return /*#__PURE__*/ _jsxs(Accordion, {
|
|
1098
|
+
disableGutters: true,
|
|
1099
|
+
elevation: 0,
|
|
1100
|
+
square: true,
|
|
1101
|
+
sx: {
|
|
1102
|
+
bgcolor: 'transparent',
|
|
1103
|
+
borderTop: 1,
|
|
1104
|
+
borderColor: 'divider',
|
|
1105
|
+
'&:last-of-type': {
|
|
1106
|
+
borderBottom: 1,
|
|
1107
|
+
borderColor: 'divider'
|
|
1108
|
+
},
|
|
1109
|
+
'&::before': {
|
|
1110
|
+
display: 'none'
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
children: [
|
|
1114
|
+
/*#__PURE__*/ _jsx(AccordionSummary, {
|
|
1115
|
+
expandIcon: /*#__PURE__*/ _jsx(SvgIcon, {
|
|
1116
|
+
fontSize: "small",
|
|
1117
|
+
children: /*#__PURE__*/ _jsx("path", {
|
|
1118
|
+
d: mdiChevronDown.path
|
|
1119
|
+
})
|
|
1120
|
+
}),
|
|
1121
|
+
sx: {
|
|
1122
|
+
px: 0
|
|
1123
|
+
},
|
|
1124
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
1125
|
+
variant: "subtitle1",
|
|
1126
|
+
component: "h2",
|
|
1127
|
+
children: title
|
|
1128
|
+
})
|
|
1129
|
+
}),
|
|
1130
|
+
/*#__PURE__*/ _jsx(AccordionDetails, {
|
|
1131
|
+
sx: {
|
|
1132
|
+
px: 0,
|
|
1133
|
+
pt: 0,
|
|
1134
|
+
pb: 2
|
|
1135
|
+
},
|
|
1136
|
+
children: children
|
|
1137
|
+
})
|
|
1138
|
+
]
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
948
1141
|
export const schema = {
|
|
949
1142
|
$id: ID,
|
|
950
1143
|
pluginId: BUNDLE_ID,
|
|
@@ -988,6 +1181,19 @@ export const schema = {
|
|
|
988
1181
|
label: 'Show discount code field',
|
|
989
1182
|
description: 'Lets a buyer enter a discount or coupon code before buying.',
|
|
990
1183
|
component: Aglyn.FieldComponentType.CHECKBOX
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
// Off by default, so no existing page changes shape on deploy.
|
|
1187
|
+
name: 'showDetails',
|
|
1188
|
+
label: 'Show details panel',
|
|
1189
|
+
description: 'A Details fold under the description with the facts the product ' + 'holds: its options, SKU, tags, and whether it is digital or a service.',
|
|
1190
|
+
component: Aglyn.FieldComponentType.CHECKBOX
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
name: 'showShipping',
|
|
1194
|
+
label: 'Show shipping & returns panel',
|
|
1195
|
+
description: 'A Shipping & returns fold: shipping priced at checkout (or digital ' + 'delivery), and your return window when your store takes returns.',
|
|
1196
|
+
component: Aglyn.FieldComponentType.CHECKBOX
|
|
991
1197
|
}
|
|
992
1198
|
]
|
|
993
1199
|
};
|