astro-tractstack 2.3.2 → 2.3.4
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/bin/create-tractstack.js +7 -4
- package/dist/index.js +51 -8
- package/package.json +1 -1
- package/templates/custom/shopify/Cart.tsx +279 -118
- package/templates/custom/shopify/CartIcon.tsx +8 -8
- package/templates/custom/shopify/CheckoutModal.tsx +328 -65
- package/templates/custom/shopify/ShopifyCartManager.tsx +117 -60
- package/templates/custom/shopify/ShopifyCheckout.tsx +2 -26
- package/templates/custom/shopify/ShopifyProductGrid.tsx +8 -0
- package/templates/custom/shopify/ShopifyServiceList.tsx +25 -37
- package/templates/custom/shopify/cart.astro +7 -1
- package/templates/src/components/Header.astro +4 -2
- package/templates/src/components/compositor/Node.tsx +39 -9
- package/templates/src/components/compositor/nodes/CreativePane.tsx +175 -88
- package/templates/src/components/edit/pane/AddPanePanel.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +6 -5
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +9 -15
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
- package/templates/src/components/form/advanced/APIConfigSection.tsx +249 -4
- package/templates/src/components/form/brand/SiteConfigSection.tsx +9 -0
- package/templates/src/components/form/shopify/SchedulingSection.tsx +44 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +66 -21
- package/templates/src/components/storykeep/Dashboard_Shopify.tsx +266 -18
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +1 -0
- package/templates/src/components/storykeep/controls/content/ProductTable.tsx +38 -24
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +240 -65
- package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +175 -48
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +91 -10
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Sales.tsx +479 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx +7 -3
- package/templates/src/constants.ts +2 -0
- package/templates/src/layouts/Layout.astro +26 -0
- package/templates/src/pages/api/auth/logout.ts +35 -2
- package/templates/src/pages/api/google/oauth/callback.ts +50 -0
- package/templates/src/pages/api/google/oauth/disconnect.ts +32 -0
- package/templates/src/pages/api/google/oauth/start.ts +32 -0
- package/templates/src/pages/api/google/oauth/status.ts +32 -0
- package/templates/src/pages/api/sales/list.ts +66 -0
- package/templates/src/pages/api/sales/metrics.ts +60 -0
- package/templates/src/pages/context/[...contextSlug].astro +50 -31
- package/templates/src/pages/privacy.astro +84 -0
- package/templates/src/pages/storykeep/advanced.astro +4 -1
- package/templates/src/pages/terms.astro +47 -0
- package/templates/src/stores/nodes.ts +8 -0
- package/templates/src/stores/shopify.ts +5 -0
- package/templates/src/types/tractstack.ts +87 -0
- package/templates/src/utils/api/advancedConfig.ts +2 -1
- package/templates/src/utils/api/advancedHelpers.ts +20 -0
- package/templates/src/utils/api/bookingHelpers.ts +3 -1
- package/templates/src/utils/api/brandConfig.ts +2 -0
- package/templates/src/utils/api/brandHelpers.ts +14 -1
- package/templates/src/utils/api/salesHelpers.ts +21 -0
- package/templates/src/utils/booking/appointmentMode.ts +135 -0
- package/templates/src/utils/customHelpers.ts +287 -2
- package/utils/inject-files.ts +47 -4
- package/templates/src/components/codehooks/SandboxAuthWrapper.tsx +0 -101
- package/templates/src/utils/actions/actionButton.ts +0 -103
- package/templates/src/utils/actions/preParse_Clicked.ts +0 -87
|
@@ -5,13 +5,15 @@ import {
|
|
|
5
5
|
cartStore,
|
|
6
6
|
modalState,
|
|
7
7
|
transactionTraceId,
|
|
8
|
-
getCartItemKey,
|
|
9
8
|
} from '@/stores/shopify';
|
|
10
9
|
import { bookingHelpers } from '@/utils/api/bookingHelpers';
|
|
11
10
|
import {
|
|
12
11
|
RESTRICTION_MESSAGES,
|
|
13
12
|
calculateCartDuration,
|
|
13
|
+
getCartItemKey,
|
|
14
|
+
isSharedFeeService,
|
|
14
15
|
} from '@/utils/customHelpers';
|
|
16
|
+
import { wouldCartHaveImpossibleRemoteMix } from '@/utils/booking/appointmentMode';
|
|
15
17
|
import type { ResourceNode } from '@/types/compositorTypes';
|
|
16
18
|
import type { CartItemState } from '@/stores/shopify';
|
|
17
19
|
import type { BrandConfigState } from '@/types/tractstack';
|
|
@@ -26,6 +28,9 @@ export default function ShopifyCartManager({
|
|
|
26
28
|
brandConfig,
|
|
27
29
|
}: ShopifyCartManagerProps) {
|
|
28
30
|
const queue = useStore(addQueue);
|
|
31
|
+
const productResources = resources.filter(
|
|
32
|
+
(r) => r.categorySlug === 'product'
|
|
33
|
+
);
|
|
29
34
|
|
|
30
35
|
useEffect(() => {
|
|
31
36
|
if (queue.length > 0) {
|
|
@@ -38,19 +43,37 @@ export default function ShopifyCartManager({
|
|
|
38
43
|
return;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
const key = getCartItemKey(actionItem);
|
|
46
|
+
const key = getCartItemKey(actionItem, resource, productResources);
|
|
42
47
|
const currentCart = cartStore.get();
|
|
43
48
|
const currentItem = currentCart[key];
|
|
44
|
-
const
|
|
49
|
+
const isSharedFeeResource = isSharedFeeService(
|
|
50
|
+
resource,
|
|
51
|
+
productResources
|
|
52
|
+
);
|
|
53
|
+
const legacySharedKeys = isSharedFeeResource
|
|
54
|
+
? Object.keys(currentCart).filter(
|
|
55
|
+
(cartKey) =>
|
|
56
|
+
cartKey !== key &&
|
|
57
|
+
currentCart[cartKey]?.resourceId === actionItem.resourceId
|
|
58
|
+
)
|
|
59
|
+
: [];
|
|
60
|
+
const legacySharedItems = legacySharedKeys
|
|
61
|
+
.map((cartKey) => currentCart[cartKey])
|
|
62
|
+
.filter((item): item is CartItemState => !!item);
|
|
63
|
+
const mergedCurrentItem = currentItem || legacySharedItems[0];
|
|
64
|
+
const currentQty = isSharedFeeResource
|
|
65
|
+
? currentItem?.quantity ||
|
|
66
|
+
legacySharedItems.reduce((total, item) => total + item.quantity, 0)
|
|
67
|
+
: currentItem?.quantity || 0;
|
|
45
68
|
const nextCart = { ...currentCart };
|
|
46
69
|
|
|
47
70
|
if (actionItem.action === 'remove') {
|
|
48
|
-
const newQty = Math.max(0, currentQty - 1);
|
|
71
|
+
const newQty = isSharedFeeResource ? 0 : Math.max(0, currentQty - 1);
|
|
49
72
|
|
|
50
73
|
if (newQty === 0) {
|
|
51
74
|
if (
|
|
52
75
|
resource?.optionsPayload?.needsBooking ||
|
|
53
|
-
|
|
76
|
+
mergedCurrentItem?.boundResourceId
|
|
54
77
|
) {
|
|
55
78
|
const traceId = transactionTraceId.get();
|
|
56
79
|
if (traceId) {
|
|
@@ -58,23 +81,34 @@ export default function ShopifyCartManager({
|
|
|
58
81
|
.releaseHold(traceId)
|
|
59
82
|
.catch((err) =>
|
|
60
83
|
console.error('Failed to release hold on cart removal:', err)
|
|
61
|
-
)
|
|
84
|
+
)
|
|
85
|
+
.finally(() => {
|
|
86
|
+
transactionTraceId.set('');
|
|
87
|
+
});
|
|
62
88
|
}
|
|
63
89
|
}
|
|
64
90
|
delete nextCart[key];
|
|
91
|
+
legacySharedKeys.forEach((legacyKey) => {
|
|
92
|
+
delete nextCart[legacyKey];
|
|
93
|
+
});
|
|
65
94
|
} else {
|
|
66
95
|
nextCart[key] = {
|
|
67
|
-
...
|
|
96
|
+
...mergedCurrentItem,
|
|
68
97
|
resourceId: actionItem.resourceId,
|
|
69
98
|
quantity: newQty,
|
|
70
99
|
};
|
|
71
100
|
}
|
|
72
101
|
|
|
73
|
-
if (
|
|
102
|
+
if (mergedCurrentItem?.boundResourceId || actionItem.boundResourceId) {
|
|
74
103
|
const boundId =
|
|
75
|
-
|
|
104
|
+
mergedCurrentItem?.boundResourceId || actionItem.boundResourceId;
|
|
76
105
|
if (boundId) {
|
|
77
|
-
const
|
|
106
|
+
const boundResource = resources.find((r) => r.id === boundId);
|
|
107
|
+
const serviceKey = getCartItemKey(
|
|
108
|
+
{ resourceId: boundId },
|
|
109
|
+
boundResource,
|
|
110
|
+
productResources
|
|
111
|
+
);
|
|
78
112
|
const serviceItem = nextCart[serviceKey];
|
|
79
113
|
if (serviceItem) {
|
|
80
114
|
const newServiceQty = Math.max(0, serviceItem.quantity - 1);
|
|
@@ -94,27 +128,39 @@ export default function ShopifyCartManager({
|
|
|
94
128
|
addQueue.set(remaining);
|
|
95
129
|
} else if (actionItem.action === 'add') {
|
|
96
130
|
transactionTraceId.set('');
|
|
97
|
-
const newQty = currentQty + 1;
|
|
131
|
+
const newQty = isSharedFeeResource ? 1 : currentQty + 1;
|
|
98
132
|
|
|
99
133
|
const newItem: CartItemState = {
|
|
100
134
|
resourceId: actionItem.resourceId,
|
|
101
135
|
quantity: newQty,
|
|
102
|
-
gid: actionItem.gid ||
|
|
103
|
-
variantId:
|
|
136
|
+
gid: actionItem.gid || mergedCurrentItem?.gid,
|
|
137
|
+
variantId: isSharedFeeResource
|
|
138
|
+
? undefined
|
|
139
|
+
: actionItem.variantId || mergedCurrentItem?.variantId,
|
|
104
140
|
variantIdShipped:
|
|
105
|
-
actionItem.variantIdShipped ||
|
|
141
|
+
actionItem.variantIdShipped || mergedCurrentItem?.variantIdShipped,
|
|
106
142
|
variantIdPickup:
|
|
107
|
-
actionItem.variantIdPickup ||
|
|
143
|
+
actionItem.variantIdPickup || mergedCurrentItem?.variantIdPickup,
|
|
108
144
|
boundResourceId:
|
|
109
|
-
actionItem.boundResourceId ||
|
|
145
|
+
actionItem.boundResourceId || mergedCurrentItem?.boundResourceId,
|
|
110
146
|
};
|
|
111
147
|
|
|
148
|
+
legacySharedKeys.forEach((legacyKey) => {
|
|
149
|
+
delete nextCart[legacyKey];
|
|
150
|
+
});
|
|
112
151
|
nextCart[key] = newItem;
|
|
113
152
|
|
|
114
153
|
if (newItem.boundResourceId) {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
154
|
+
const boundResource = resources.find(
|
|
155
|
+
(r) => r.id === newItem.boundResourceId
|
|
156
|
+
);
|
|
157
|
+
const serviceKey = getCartItemKey(
|
|
158
|
+
{
|
|
159
|
+
resourceId: newItem.boundResourceId,
|
|
160
|
+
},
|
|
161
|
+
boundResource,
|
|
162
|
+
productResources
|
|
163
|
+
);
|
|
118
164
|
const serviceItem = nextCart[serviceKey];
|
|
119
165
|
|
|
120
166
|
if (serviceItem) {
|
|
@@ -130,54 +176,65 @@ export default function ShopifyCartManager({
|
|
|
130
176
|
}
|
|
131
177
|
}
|
|
132
178
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const interval = 15;
|
|
136
|
-
const snappedDuration = Math.ceil(rawDuration / interval) * interval;
|
|
137
|
-
|
|
138
|
-
const dynamicMax = brandConfig?.scheduling?.maxLengthMinutes || 180;
|
|
139
|
-
if (snappedDuration > dynamicMax) {
|
|
179
|
+
if (wouldCartHaveImpossibleRemoteMix(nextCart, resources)) {
|
|
140
180
|
modalState.set({
|
|
141
181
|
isOpen: true,
|
|
142
182
|
type: 'restriction',
|
|
143
|
-
title: '
|
|
144
|
-
message: RESTRICTION_MESSAGES.
|
|
183
|
+
title: 'Incompatible Booking Modes',
|
|
184
|
+
message: RESTRICTION_MESSAGES.INCOMPATIBLE_REMOTE,
|
|
145
185
|
});
|
|
146
186
|
} else {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
187
|
+
const rawDuration = calculateCartDuration(nextCart, resources);
|
|
188
|
+
|
|
189
|
+
const interval = 15;
|
|
190
|
+
const snappedDuration = Math.ceil(rawDuration / interval) * interval;
|
|
191
|
+
|
|
192
|
+
const dynamicMax = brandConfig?.scheduling?.maxLengthMinutes || 180;
|
|
193
|
+
if (snappedDuration > dynamicMax) {
|
|
194
|
+
modalState.set({
|
|
195
|
+
isOpen: true,
|
|
196
|
+
type: 'restriction',
|
|
197
|
+
title: 'Appointment Length Limit Reached',
|
|
198
|
+
message: RESTRICTION_MESSAGES.MAX_DURATION(dynamicMax),
|
|
199
|
+
});
|
|
200
|
+
} else {
|
|
201
|
+
cartStore.set(nextCart);
|
|
202
|
+
|
|
203
|
+
if (!actionItem.suppressModal) {
|
|
204
|
+
let targetResource = resource;
|
|
205
|
+
if (newItem.boundResourceId) {
|
|
206
|
+
const bound = resources.find(
|
|
207
|
+
(r) => r.id === newItem.boundResourceId
|
|
208
|
+
);
|
|
209
|
+
if (bound) {
|
|
210
|
+
targetResource = bound;
|
|
211
|
+
}
|
|
157
212
|
}
|
|
158
|
-
}
|
|
159
213
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
214
|
+
if (
|
|
215
|
+
targetResource.categorySlug === 'service' ||
|
|
216
|
+
targetResource.optionsPayload?.needsBooking
|
|
217
|
+
) {
|
|
218
|
+
modalState.set({
|
|
219
|
+
isOpen: true,
|
|
220
|
+
type: 'success',
|
|
221
|
+
title: 'Booking Required',
|
|
222
|
+
message: RESTRICTION_MESSAGES.BOOKING(
|
|
223
|
+
(
|
|
224
|
+
targetResource.optionsPayload?.bookingLengthMinutes || 0
|
|
225
|
+
).toString()
|
|
226
|
+
),
|
|
227
|
+
});
|
|
228
|
+
} else {
|
|
229
|
+
modalState.set({
|
|
230
|
+
isOpen: true,
|
|
231
|
+
type: 'success',
|
|
232
|
+
title: 'Added to Cart',
|
|
233
|
+
message: RESTRICTION_MESSAGES.DEFAULT_ADD(
|
|
234
|
+
targetResource.title
|
|
235
|
+
),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
181
238
|
}
|
|
182
239
|
}
|
|
183
240
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
CART_STATES,
|
|
7
7
|
isShopifyHandoff,
|
|
8
8
|
} from '@/stores/shopify';
|
|
9
|
+
import { buildShopifyCheckoutLines } from '@/utils/customHelpers';
|
|
9
10
|
import type { ResourceNode } from '@/types/compositorTypes';
|
|
10
11
|
|
|
11
12
|
interface ShopifyCheckoutProps {
|
|
@@ -33,32 +34,7 @@ export default function ShopifyCheckout({
|
|
|
33
34
|
setStatus('PROCESSING');
|
|
34
35
|
|
|
35
36
|
try {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const lines = cartItems
|
|
39
|
-
.map((item) => {
|
|
40
|
-
// Resolve the ResourceNode for this item
|
|
41
|
-
const resource = resources.find((r) => r.id === item.resourceId);
|
|
42
|
-
|
|
43
|
-
if (!resource) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Use the explicitly sanitized variant ID from the cart state
|
|
48
|
-
const merchandiseId = item.variantId;
|
|
49
|
-
|
|
50
|
-
// If we have no ID, we cannot add this item to the Shopify cart.
|
|
51
|
-
if (!merchandiseId) return null;
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
merchandiseId,
|
|
55
|
-
quantity: item.quantity,
|
|
56
|
-
};
|
|
57
|
-
})
|
|
58
|
-
.filter((line) => line !== null) as Array<{
|
|
59
|
-
merchandiseId: string;
|
|
60
|
-
quantity: number;
|
|
61
|
-
}>;
|
|
37
|
+
const lines = buildShopifyCheckoutLines(cart, resources);
|
|
62
38
|
|
|
63
39
|
if (lines.length === 0) {
|
|
64
40
|
throw new Error(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { useStore } from '@nanostores/react';
|
|
3
3
|
import { cartStore, addQueue, type CartAction } from '@/stores/shopify';
|
|
4
|
+
import { collectServiceGids } from '@/utils/customHelpers';
|
|
4
5
|
import { getShopifyImage } from '@/utils/helpers';
|
|
5
6
|
import type { ResourceNode } from '@/types/compositorTypes';
|
|
6
7
|
|
|
@@ -240,6 +241,13 @@ export default function ShopifyProductGrid({ resources = {}, options }: Props) {
|
|
|
240
241
|
products = products.filter((p) => p.optionsPayload?.group === group);
|
|
241
242
|
}
|
|
242
243
|
|
|
244
|
+
const serviceGids = collectServiceGids(services);
|
|
245
|
+
products = products.filter((p) => {
|
|
246
|
+
const gid =
|
|
247
|
+
typeof p.optionsPayload?.gid === 'string' ? p.optionsPayload.gid : '';
|
|
248
|
+
return gid === '' || !serviceGids.has(gid);
|
|
249
|
+
});
|
|
250
|
+
|
|
243
251
|
if (products.length === 0) return null;
|
|
244
252
|
|
|
245
253
|
return (
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { useStore } from '@nanostores/react';
|
|
2
|
+
import { cartStore, addQueue, type CartAction } from '@/stores/shopify';
|
|
2
3
|
import {
|
|
3
|
-
cartStore,
|
|
4
|
-
addQueue,
|
|
5
4
|
getCartItemKey,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
getServiceVariantIdFromCanonicalProduct,
|
|
6
|
+
isSharedFeeService,
|
|
7
|
+
} from '@/utils/customHelpers';
|
|
8
8
|
import type { ResourceNode } from '@/types/compositorTypes';
|
|
9
9
|
|
|
10
10
|
interface Props {
|
|
@@ -55,33 +55,17 @@ export default function ShopifyServiceList({ resources = {}, options }: Props) {
|
|
|
55
55
|
(s) => !boundServiceSlugs.has(s.slug)
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
-
const getServiceVariantId = (resource: ResourceNode): string | undefined => {
|
|
59
|
-
try {
|
|
60
|
-
if (resource.optionsPayload?.shopifyData) {
|
|
61
|
-
const data = JSON.parse(resource.optionsPayload.shopifyData);
|
|
62
|
-
// Handle both raw product data and simplified product objects
|
|
63
|
-
const product = data.products?.[0] || data;
|
|
64
|
-
return product?.variants?.[0]?.id;
|
|
65
|
-
}
|
|
66
|
-
} catch (e) {
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
return undefined;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
58
|
const handleToggle = (resource: ResourceNode, currentQuantity: number) => {
|
|
73
59
|
const actionType = currentQuantity > 0 ? 'remove' : 'add';
|
|
60
|
+
const gid =
|
|
61
|
+
typeof resource.optionsPayload?.gid === 'string'
|
|
62
|
+
? resource.optionsPayload.gid
|
|
63
|
+
: undefined;
|
|
74
64
|
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (resource.optionsPayload?.shopifyData) {
|
|
80
|
-
const data = JSON.parse(resource.optionsPayload.shopifyData);
|
|
81
|
-
const product = data.products?.[0] || data;
|
|
82
|
-
gid = product?.id;
|
|
83
|
-
}
|
|
84
|
-
} catch (e) {}
|
|
65
|
+
const sharedFee = isSharedFeeService(resource, products);
|
|
66
|
+
const variantId = sharedFee
|
|
67
|
+
? undefined
|
|
68
|
+
: getServiceVariantIdFromCanonicalProduct(resource, products);
|
|
85
69
|
|
|
86
70
|
const newAction: CartAction = {
|
|
87
71
|
resourceId: resource.id,
|
|
@@ -115,14 +99,20 @@ export default function ShopifyServiceList({ resources = {}, options }: Props) {
|
|
|
115
99
|
<section className="w-full">
|
|
116
100
|
<div className="space-y-4">
|
|
117
101
|
{displayServices.map((resource) => {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
102
|
+
const key = getCartItemKey(
|
|
103
|
+
{ resourceId: resource.id },
|
|
104
|
+
resource,
|
|
105
|
+
products
|
|
106
|
+
);
|
|
123
107
|
|
|
124
108
|
const cartItem = cart[key];
|
|
125
|
-
const
|
|
109
|
+
const legacyCartItem = Object.values(cart).find(
|
|
110
|
+
(item) =>
|
|
111
|
+
item.resourceId === resource.id && (item.quantity || 0) > 0
|
|
112
|
+
);
|
|
113
|
+
const selectedQuantity =
|
|
114
|
+
cartItem?.quantity || legacyCartItem?.quantity || 0;
|
|
115
|
+
const isSelected = selectedQuantity > 0;
|
|
126
116
|
const duration = resource.optionsPayload?.bookingLengthMinutes;
|
|
127
117
|
|
|
128
118
|
return (
|
|
@@ -152,9 +142,7 @@ export default function ShopifyServiceList({ resources = {}, options }: Props) {
|
|
|
152
142
|
|
|
153
143
|
<div className="ml-4 flex-shrink-0">
|
|
154
144
|
<button
|
|
155
|
-
onClick={() =>
|
|
156
|
-
handleToggle(resource, cartItem?.quantity || 0)
|
|
157
|
-
}
|
|
145
|
+
onClick={() => handleToggle(resource, selectedQuantity)}
|
|
158
146
|
className={`relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
|
|
159
147
|
isSelected ? 'bg-black' : 'bg-gray-200'
|
|
160
148
|
}`}
|
|
@@ -18,6 +18,12 @@ const resources = await getHeaderResources(tenantId, resourceCategories);
|
|
|
18
18
|
|
|
19
19
|
<Layout title="Your Cart" slug="cart">
|
|
20
20
|
<main class="mx-auto max-w-7xl px-4 py-16 md:px-6 xl:px-8">
|
|
21
|
-
<Cart
|
|
21
|
+
<Cart
|
|
22
|
+
resources={resources}
|
|
23
|
+
allowRemote={brandConfig?.SCHEDULING?.allowRemote || false}
|
|
24
|
+
remoteOnly={brandConfig?.SCHEDULING?.remoteOnly || false}
|
|
25
|
+
embedded={true}
|
|
26
|
+
client:only="react"
|
|
27
|
+
/>
|
|
22
28
|
</main>
|
|
23
29
|
</Layout>
|
|
@@ -76,13 +76,15 @@ if (hasShopify) {
|
|
|
76
76
|
<>
|
|
77
77
|
{slug !== `cart` ? (
|
|
78
78
|
<div class="flex w-full justify-end px-4 py-2 md:px-8">
|
|
79
|
-
<CartIcon client:only="react" />
|
|
79
|
+
<CartIcon client:only="react" resources={shopifyResources} />
|
|
80
80
|
</div>
|
|
81
81
|
) : (
|
|
82
82
|
<CheckoutModal
|
|
83
83
|
client:only="react"
|
|
84
84
|
resources={shopifyResources}
|
|
85
|
-
maxLength={brandConfig?.
|
|
85
|
+
maxLength={brandConfig?.SCHEDULING?.maxLengthMinutes || 180}
|
|
86
|
+
allowRemote={brandConfig?.SCHEDULING?.allowRemote || false}
|
|
87
|
+
remoteOnly={brandConfig?.SCHEDULING?.remoteOnly || false}
|
|
86
88
|
/>
|
|
87
89
|
)}
|
|
88
90
|
</>
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isTopLevelBlockNode,
|
|
9
9
|
parseCodeHook,
|
|
10
10
|
} from '@/utils/compositor/nodesHelper';
|
|
11
|
+
import { isStoryFragmentNode } from '@/utils/compositor/typeGuards';
|
|
11
12
|
import { PaneAddMode } from '@/types/compositorTypes';
|
|
12
13
|
import { NodeOverlay } from './tools/NodeOverlay';
|
|
13
14
|
import PanelVisibilityWrapper from '@/components/compositor/PanelVisibilityWrapper';
|
|
@@ -108,14 +109,6 @@ export const Node = memo((props: NodeProps) => {
|
|
|
108
109
|
case 'Pane':
|
|
109
110
|
{
|
|
110
111
|
const paneNode = node as PaneNode;
|
|
111
|
-
const storyfragmentNodeId = ctx.getClosestNodeTypeFromId(
|
|
112
|
-
node.id,
|
|
113
|
-
'StoryFragment'
|
|
114
|
-
);
|
|
115
|
-
const storyfragmentNode = ctx.allNodes
|
|
116
|
-
.get()
|
|
117
|
-
.get(storyfragmentNodeId) as StoryFragmentNode;
|
|
118
|
-
const first = paneNode.id === storyfragmentNode.paneIds?.[0];
|
|
119
112
|
const isHtmlAstPane = !!paneNode.htmlAst;
|
|
120
113
|
const paneNodes = ctx.getChildNodeIDs(node.id);
|
|
121
114
|
|
|
@@ -140,7 +133,7 @@ export const Node = memo((props: NodeProps) => {
|
|
|
140
133
|
);
|
|
141
134
|
}
|
|
142
135
|
|
|
143
|
-
if (!isPreview && !paneNodes.length) {
|
|
136
|
+
if (!isPreview && !paneNodes.length && !isHtmlAstPane) {
|
|
144
137
|
return (
|
|
145
138
|
<>
|
|
146
139
|
<ContextPanePanel nodeId={node.id} />
|
|
@@ -160,8 +153,45 @@ export const Node = memo((props: NodeProps) => {
|
|
|
160
153
|
</>
|
|
161
154
|
);
|
|
162
155
|
}
|
|
156
|
+
|
|
157
|
+
const contextContent = isHtmlAstPane ? (
|
|
158
|
+
<CreativePane nodeId={props.nodeId} htmlAst={paneNode.htmlAst!} />
|
|
159
|
+
) : (
|
|
160
|
+
<Pane {...props} />
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
element = (
|
|
164
|
+
<>
|
|
165
|
+
<div className="py-0.5">
|
|
166
|
+
<ConfigPanePanel
|
|
167
|
+
nodeId={props.nodeId}
|
|
168
|
+
isHtmlAstPane={isHtmlAstPane}
|
|
169
|
+
isSandboxMode={props.isSandboxMode || false}
|
|
170
|
+
/>
|
|
171
|
+
<PanelVisibilityWrapper
|
|
172
|
+
nodeId={props.nodeId}
|
|
173
|
+
panelType="settings"
|
|
174
|
+
ctx={ctx}
|
|
175
|
+
>
|
|
176
|
+
{contextContent}
|
|
177
|
+
</PanelVisibilityWrapper>
|
|
178
|
+
</div>
|
|
179
|
+
</>
|
|
180
|
+
);
|
|
181
|
+
break;
|
|
163
182
|
}
|
|
164
183
|
|
|
184
|
+
const storyfragmentNodeId = ctx.getClosestNodeTypeFromId(
|
|
185
|
+
node.id,
|
|
186
|
+
'StoryFragment'
|
|
187
|
+
);
|
|
188
|
+
const storyfragmentNode = storyfragmentNodeId
|
|
189
|
+
? (ctx.allNodes.get().get(storyfragmentNodeId) ?? null)
|
|
190
|
+
: null;
|
|
191
|
+
const first =
|
|
192
|
+
isStoryFragmentNode(storyfragmentNode) &&
|
|
193
|
+
paneNode.id === storyfragmentNode.paneIds?.[0];
|
|
194
|
+
|
|
165
195
|
const content = isHtmlAstPane ? (
|
|
166
196
|
<CreativePane nodeId={props.nodeId} htmlAst={paneNode.htmlAst!} />
|
|
167
197
|
) : (
|