astro-tractstack 2.3.0 → 2.3.1
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/README.md +1 -1
- package/bin/create-tractstack.js +2 -2
- package/dist/index.js +94 -16
- package/package.json +2 -2
- package/templates/custom/minimal/CodeHook.astro +10 -2
- package/templates/custom/shopify/Cart.tsx +100 -73
- package/templates/custom/shopify/CheckoutModal.tsx +509 -120
- package/templates/custom/shopify/NativeBookingCalendar.tsx +375 -0
- package/templates/custom/shopify/ShopifyCartManager.tsx +92 -37
- package/templates/custom/shopify/ShopifyProductGrid.tsx +139 -173
- package/templates/custom/shopify/ShopifyServiceList.tsx +20 -3
- package/templates/custom/with-examples/CodeHook.astro +10 -2
- package/templates/src/components/Footer.astro +4 -4
- package/templates/src/components/Header.astro +9 -3
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +3 -3
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +2 -2
- package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +2 -2
- package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +3 -3
- package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +2 -2
- package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +7 -7
- package/templates/src/components/form/advanced/APIConfigSection.tsx +244 -2
- package/templates/src/components/form/shopify/SchedulingSection.tsx +354 -0
- package/templates/src/components/storykeep/Dashboard.tsx +1 -1
- package/templates/src/components/storykeep/Dashboard_Shopify.tsx +253 -110
- package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +14 -5
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +5 -2
- package/templates/src/components/storykeep/controls/content/MenuTable.tsx +14 -5
- package/templates/src/components/storykeep/controls/content/ProductTable.tsx +180 -101
- package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +9 -5
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +13 -4
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +14 -5
- package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +111 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +393 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Products.tsx +46 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Schedule.tsx +78 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx +55 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Services.tsx +47 -0
- package/templates/src/pages/api/auth/lookup-lead.ts +72 -0
- package/templates/src/pages/api/booking/availability.ts +72 -0
- package/templates/src/pages/api/booking/cancel.ts +73 -0
- package/templates/src/pages/api/booking/confirm.ts +82 -0
- package/templates/src/pages/api/booking/hold.ts +75 -0
- package/templates/src/pages/api/booking/list.ts +66 -0
- package/templates/src/pages/api/booking/metrics.ts +60 -0
- package/templates/src/pages/api/booking/release.ts +76 -0
- package/templates/src/pages/api/sandbox.ts +2 -2
- package/templates/src/pages/api/shopify/createCart.ts +4 -8
- package/templates/src/pages/api/shopify/getProducts.ts +15 -15
- package/templates/src/pages/storykeep/login.astro +21 -14
- package/templates/src/stores/shopify.ts +81 -25
- package/templates/src/types/tractstack.ts +54 -0
- package/templates/src/utils/api/advancedConfig.ts +2 -0
- package/templates/src/utils/api/advancedHelpers.ts +40 -3
- package/templates/src/utils/api/bookingHelpers.ts +125 -0
- package/templates/src/utils/api/brandHelpers.ts +10 -0
- package/templates/src/utils/auth.ts +29 -9
- package/templates/src/utils/compositor/aiGeneration.ts +3 -3
- package/templates/src/utils/compositor/aiPaneParser.ts +2 -2
- package/templates/src/utils/customHelpers.ts +0 -21
- package/templates/src/utils/profileStorage.ts +5 -0
- package/templates/src/utils/tenantResolver.ts +2 -1
- package/utils/inject-files.ts +82 -4
- package/templates/custom/shopify/CalDotComBooking.tsx +0 -44
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ For more recipes and community guides, visit [FreeWebPress.org](https://FreeWebP
|
|
|
75
75
|
|
|
76
76
|
Expanding full-text search to include embedded video transcripts (via BunnyCDN).
|
|
77
77
|
|
|
78
|
-
- **AI Visual Editor:** Powered by AssemblyAI
|
|
78
|
+
- **AI Visual Editor:** Powered by AssemblyAI aai to highlight and extract transcript sections.
|
|
79
79
|
- **Smart Content:** Automated chaptering and descriptions for long-form video.
|
|
80
80
|
- **Video-to-Blog Pipeline:** Transform video highlights into rich, searchable blog pages using the design library.
|
|
81
81
|
|
package/bin/create-tractstack.js
CHANGED
|
@@ -348,7 +348,7 @@ PUBLIC_ENABLE_BUNNY="${finalResponses.enableBunny ? 'true' : 'false'}"
|
|
|
348
348
|
|
|
349
349
|
// Install UI components
|
|
350
350
|
execSync(
|
|
351
|
-
`${addCommand} @ark-ui/react@^5.30.0 @heroicons/react@^2.1.1 @internationalized/date@3.10.0
|
|
351
|
+
`${addCommand} @ark-ui/react@^5.30.0 @heroicons/react@^2.1.1 @internationalized/date@3.10.0`,
|
|
352
352
|
{
|
|
353
353
|
stdio: 'inherit',
|
|
354
354
|
}
|
|
@@ -382,7 +382,7 @@ PUBLIC_ENABLE_BUNNY="${finalResponses.enableBunny ? 'true' : 'false'}"
|
|
|
382
382
|
console.log('Please run manually:');
|
|
383
383
|
console.log(
|
|
384
384
|
kleur.cyan(
|
|
385
|
-
`${addCommand} react@^19.0.0 react-dom@^19.0.0 @astrojs/react@^4.4.2 @astrojs/node@^9.4.3 @nanostores/react@^1.0.0 nanostores@^1.0.1 @nanostores/persistent ulid@^3.0.1 @ark-ui/react@^5.30.0 @heroicons/react@^2.1.1 @internationalized/date@3.10.0
|
|
385
|
+
`${addCommand} react@^19.0.0 react-dom@^19.0.0 @astrojs/react@^4.4.2 @astrojs/node@^9.4.3 @nanostores/react@^1.0.0 nanostores@^1.0.1 @nanostores/persistent ulid@^3.0.1 @ark-ui/react@^5.30.0 @heroicons/react@^2.1.1 @internationalized/date@3.10.0 d3@^7.9.0 d3-sankey@^0.12.3 recharts@^3.1.2 player.js@^0.1.0 tinycolor2@1.6.0 html-to-image@^1.11.13 path-to-regexp@^8.0.0 postcss postcss-selector-parser`
|
|
386
386
|
)
|
|
387
387
|
);
|
|
388
388
|
console.log(
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ function b(t) {
|
|
|
10
10
|
}
|
|
11
11
|
function g(t, e) {
|
|
12
12
|
e.info("TractStack configuration applied"), t.enableMultiTenant && e.info("Multi-tenant mode enabled"), t.includeExamples && e.info("Example components will be included");
|
|
13
|
-
const c = process.env.PUBLIC_GO_BACKEND,
|
|
13
|
+
const c = process.env.PUBLIC_GO_BACKEND, o = process.env.PUBLIC_TENANTID;
|
|
14
14
|
if (!c)
|
|
15
15
|
e.warn("PUBLIC_GO_BACKEND not set - this will be required at runtime");
|
|
16
16
|
else
|
|
@@ -19,11 +19,11 @@ function g(t, e) {
|
|
|
19
19
|
} catch {
|
|
20
20
|
e.error(`PUBLIC_GO_BACKEND is not a valid URL: ${c}`);
|
|
21
21
|
}
|
|
22
|
-
return
|
|
22
|
+
return o ? /^[a-zA-Z0-9_-]+$/.test(o) ? e.info(`Tenant ID validated: ${o}`) : e.error(`PUBLIC_TENANTID contains invalid characters: ${o}`) : e.warn("PUBLIC_TENANTID not set - this will be required at runtime"), t;
|
|
23
23
|
}
|
|
24
24
|
async function y(t, e, c) {
|
|
25
25
|
e.info("TractStack: Injecting template files");
|
|
26
|
-
const
|
|
26
|
+
const o = [
|
|
27
27
|
// Core Configuration
|
|
28
28
|
{
|
|
29
29
|
src: t("../templates/env.example"),
|
|
@@ -815,6 +815,10 @@ async function y(t, e, c) {
|
|
|
815
815
|
src: t("../templates/src/utils/api/resourceHelpers.ts"),
|
|
816
816
|
dest: "src/utils/api/resourceHelpers.ts"
|
|
817
817
|
},
|
|
818
|
+
{
|
|
819
|
+
src: t("../templates/src/utils/api/bookingHelpers.ts"),
|
|
820
|
+
dest: "src/utils/api/bookingHelpers.ts"
|
|
821
|
+
},
|
|
818
822
|
{
|
|
819
823
|
src: t("../templates/src/utils/api/menuHelpers.ts"),
|
|
820
824
|
dest: "src/utils/api/menuHelpers.ts"
|
|
@@ -937,6 +941,38 @@ async function y(t, e, c) {
|
|
|
937
941
|
dest: "src/pages/storykeep/profile.astro"
|
|
938
942
|
},
|
|
939
943
|
// API Routes
|
|
944
|
+
{
|
|
945
|
+
src: t("../templates/src/pages/api/booking/list.ts"),
|
|
946
|
+
dest: "src/pages/api/booking/list.ts"
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
src: t("../templates/src/pages/api/booking/metrics.ts"),
|
|
950
|
+
dest: "src/pages/api/booking/metrics.ts"
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
src: t("../templates/src/pages/api/booking/cancel.ts"),
|
|
954
|
+
dest: "src/pages/api/booking/cancel.ts"
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
src: t("../templates/src/pages/api/booking/confirm.ts"),
|
|
958
|
+
dest: "src/pages/api/booking/confirm.ts"
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
src: t("../templates/src/pages/api/booking/release.ts"),
|
|
962
|
+
dest: "src/pages/api/booking/release.ts"
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
src: t("../templates/src/pages/api/booking/availability.ts"),
|
|
966
|
+
dest: "src/pages/api/booking/availability.ts"
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
src: t("../templates/src/pages/api/booking/hold.ts"),
|
|
970
|
+
dest: "src/pages/api/booking/hold.ts"
|
|
971
|
+
},
|
|
972
|
+
{
|
|
973
|
+
src: t("../templates/src/pages/api/auth/lookup-lead.ts"),
|
|
974
|
+
dest: "src/pages/api/auth/lookup-lead.ts"
|
|
975
|
+
},
|
|
940
976
|
{
|
|
941
977
|
src: t("../templates/src/pages/api/auth/profile.ts"),
|
|
942
978
|
dest: "src/pages/api/auth/profile.ts"
|
|
@@ -1118,6 +1154,12 @@ async function y(t, e, c) {
|
|
|
1118
1154
|
src: t("../templates/src/components/form/brand/SEOSection.tsx"),
|
|
1119
1155
|
dest: "src/components/form/brand/SEOSection.tsx"
|
|
1120
1156
|
},
|
|
1157
|
+
{
|
|
1158
|
+
src: t(
|
|
1159
|
+
"../templates/src/components/form/shopify/SchedulingSection.tsx"
|
|
1160
|
+
),
|
|
1161
|
+
dest: "src/components/form/shopify/SchedulingSection.tsx"
|
|
1162
|
+
},
|
|
1121
1163
|
// Advanced Configuration Components
|
|
1122
1164
|
{
|
|
1123
1165
|
src: t(
|
|
@@ -1170,6 +1212,42 @@ async function y(t, e, c) {
|
|
|
1170
1212
|
),
|
|
1171
1213
|
dest: "src/components/storykeep/Dashboard_Shopify.tsx"
|
|
1172
1214
|
},
|
|
1215
|
+
{
|
|
1216
|
+
src: t(
|
|
1217
|
+
"../templates/src/components/storykeep/shopify/ShopifyDashboard.tsx"
|
|
1218
|
+
),
|
|
1219
|
+
dest: "src/components/storykeep/shopify/ShopifyDashboard.tsx"
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
src: t(
|
|
1223
|
+
"../templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx"
|
|
1224
|
+
),
|
|
1225
|
+
dest: "src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx"
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
src: t(
|
|
1229
|
+
"../templates/src/components/storykeep/shopify/ShopifyDashboard_Schedule.tsx"
|
|
1230
|
+
),
|
|
1231
|
+
dest: "src/components/storykeep/shopify/ShopifyDashboard_Schedule.tsx"
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
src: t(
|
|
1235
|
+
"../templates/src/components/storykeep/shopify/ShopifyDashboard_Products.tsx"
|
|
1236
|
+
),
|
|
1237
|
+
dest: "src/components/storykeep/shopify/ShopifyDashboard_Products.tsx"
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
src: t(
|
|
1241
|
+
"../templates/src/components/storykeep/shopify/ShopifyDashboard_Services.tsx"
|
|
1242
|
+
),
|
|
1243
|
+
dest: "src/components/storykeep/shopify/ShopifyDashboard_Services.tsx"
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
src: t(
|
|
1247
|
+
"../templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx"
|
|
1248
|
+
),
|
|
1249
|
+
dest: "src/components/storykeep/shopify/ShopifyDashboard_Search.tsx"
|
|
1250
|
+
},
|
|
1173
1251
|
{
|
|
1174
1252
|
src: t(
|
|
1175
1253
|
"../templates/src/components/storykeep/Dashboard_Analytics.tsx"
|
|
@@ -2231,13 +2309,13 @@ async function y(t, e, c) {
|
|
|
2231
2309
|
protected: !0
|
|
2232
2310
|
},
|
|
2233
2311
|
{
|
|
2234
|
-
src: t("../templates/custom/shopify/
|
|
2235
|
-
dest: "src/custom/shopify/
|
|
2312
|
+
src: t("../templates/custom/shopify/ShopifyCheckout.tsx"),
|
|
2313
|
+
dest: "src/custom/shopify/ShopifyCheckout.tsx",
|
|
2236
2314
|
protected: !0
|
|
2237
2315
|
},
|
|
2238
2316
|
{
|
|
2239
|
-
src: t("../templates/custom/shopify/
|
|
2240
|
-
dest: "src/custom/shopify/
|
|
2317
|
+
src: t("../templates/custom/shopify/NativeBookingCalendar.tsx"),
|
|
2318
|
+
dest: "src/custom/shopify/NativeBookingCalendar.tsx",
|
|
2241
2319
|
protected: !0
|
|
2242
2320
|
},
|
|
2243
2321
|
// Example Components (Conditional)
|
|
@@ -2285,12 +2363,12 @@ async function y(t, e, c) {
|
|
|
2285
2363
|
}
|
|
2286
2364
|
] : []
|
|
2287
2365
|
];
|
|
2288
|
-
for (const s of
|
|
2366
|
+
for (const s of o)
|
|
2289
2367
|
try {
|
|
2290
2368
|
const p = i(s.dest);
|
|
2291
2369
|
n(p) || x(p, { recursive: !0 });
|
|
2292
|
-
const
|
|
2293
|
-
if (!n(s.dest) ||
|
|
2370
|
+
const r = !s.protected && (s.dest === "tailwind.config.cjs" || s.dest.startsWith("src/components/codehooks/") || s.dest.startsWith("src/components/widgets/") || s.dest.startsWith("src/") || s.dest.startsWith("public/client/") || s.dest === ".gitignore");
|
|
2371
|
+
if (!n(s.dest) || r)
|
|
2294
2372
|
if (n(s.src))
|
|
2295
2373
|
k(s.src, s.dest), e.info(`Updated ${s.dest}`);
|
|
2296
2374
|
else {
|
|
@@ -2299,8 +2377,8 @@ async function y(t, e, c) {
|
|
|
2299
2377
|
}
|
|
2300
2378
|
else s.protected ? e.info(`Protected: ${s.dest} (skipped overwrite)`) : e.info(`Skipped existing ${s.dest}`);
|
|
2301
2379
|
} catch (p) {
|
|
2302
|
-
const
|
|
2303
|
-
e.error(`Failed to create ${s.dest}: ${
|
|
2380
|
+
const r = p instanceof Error ? p.message : String(p);
|
|
2381
|
+
e.error(`Failed to create ${s.dest}: ${r}`);
|
|
2304
2382
|
}
|
|
2305
2383
|
}
|
|
2306
2384
|
function w(t) {
|
|
@@ -2314,12 +2392,12 @@ export default function Placeholder() {
|
|
|
2314
2392
|
}` : t.endsWith(".ts") ? `// TractStack placeholder utility
|
|
2315
2393
|
export const placeholder = "${t}";` : `# TractStack placeholder: ${t}`;
|
|
2316
2394
|
}
|
|
2317
|
-
function
|
|
2395
|
+
function P(t = {}) {
|
|
2318
2396
|
const { resolve: e } = b(import.meta.url);
|
|
2319
2397
|
return {
|
|
2320
2398
|
name: "astro-tractstack",
|
|
2321
2399
|
hooks: {
|
|
2322
|
-
"astro:config:setup": async ({ config: c, updateConfig:
|
|
2400
|
+
"astro:config:setup": async ({ config: c, updateConfig: o, logger: s }) => {
|
|
2323
2401
|
g(t, s);
|
|
2324
2402
|
const p = t.enableMultiTenant || !1;
|
|
2325
2403
|
if (s.info(
|
|
@@ -2339,7 +2417,7 @@ function h(t = {}) {
|
|
|
2339
2417
|
), new Error(
|
|
2340
2418
|
"TractStack requires an SSR adapter. Please add @astrojs/node adapter to your astro.config.mjs"
|
|
2341
2419
|
);
|
|
2342
|
-
|
|
2420
|
+
o({
|
|
2343
2421
|
vite: {
|
|
2344
2422
|
define: {
|
|
2345
2423
|
__TRACTSTACK_VERSION__: JSON.stringify("2.0.0-alpha.1"),
|
|
@@ -2396,5 +2474,5 @@ function h(t = {}) {
|
|
|
2396
2474
|
};
|
|
2397
2475
|
}
|
|
2398
2476
|
export {
|
|
2399
|
-
|
|
2477
|
+
P as default
|
|
2400
2478
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-tractstack",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Astro integration for TractStack - the free web press by At Risk Media",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@heroicons/react": "^2.1.1",
|
|
62
62
|
"@internationalized/date": "^3.10.1",
|
|
63
63
|
"@mhsdesign/jit-browser-tailwindcss": "^0.4.2",
|
|
64
|
-
"@nanostores/persistent": "^1.
|
|
64
|
+
"@nanostores/persistent": "^1.3.3",
|
|
65
65
|
"@nanostores/react": "^1.0.0",
|
|
66
66
|
"@types/d3": "^7.4.3",
|
|
67
67
|
"@types/d3-sankey": "^0.12.5",
|
|
@@ -50,9 +50,17 @@ export const components = {
|
|
|
50
50
|
) : target === 'epinet' ? (
|
|
51
51
|
<EpinetWrapper fullContentMap={fullContentMap} client:only="react" />
|
|
52
52
|
) : target === 'shopify-product-grid' ? (
|
|
53
|
-
<ShopifyProductGrid
|
|
53
|
+
<ShopifyProductGrid
|
|
54
|
+
options={options}
|
|
55
|
+
resources={resourcesPayload}
|
|
56
|
+
client:only="react"
|
|
57
|
+
/>
|
|
54
58
|
) : target === 'shopify-service-list' ? (
|
|
55
|
-
<ShopifyServiceList
|
|
59
|
+
<ShopifyServiceList
|
|
60
|
+
options={options}
|
|
61
|
+
resources={resourcesPayload}
|
|
62
|
+
client:only="react"
|
|
63
|
+
/>
|
|
56
64
|
) : (
|
|
57
65
|
/* : target === "custom-hero" ? (
|
|
58
66
|
<CustomHero />
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
+
import { ulid } from 'ulid';
|
|
2
3
|
import { useStore } from '@nanostores/react';
|
|
3
4
|
import {
|
|
4
5
|
addQueue,
|
|
@@ -6,7 +7,7 @@ import {
|
|
|
6
7
|
cartState,
|
|
7
8
|
CART_STATES,
|
|
8
9
|
isShopifyHandoff,
|
|
9
|
-
|
|
10
|
+
transactionTraceId,
|
|
10
11
|
type CartItemState,
|
|
11
12
|
} from '@/stores/shopify';
|
|
12
13
|
import { getShopifyImage } from '@/utils/helpers';
|
|
@@ -18,12 +19,19 @@ interface CartProps {
|
|
|
18
19
|
|
|
19
20
|
const getCleanVariantTitle = (variant: any) => {
|
|
20
21
|
if (variant?.selectedOptions) {
|
|
21
|
-
|
|
22
|
-
.filter(
|
|
22
|
+
const filtered = variant.selectedOptions
|
|
23
|
+
.filter(
|
|
24
|
+
(o: any) =>
|
|
25
|
+
o.name !== 'Mode' && o.name !== 'Title' && o.value !== 'Default Title'
|
|
26
|
+
)
|
|
23
27
|
.map((o: any) => o.value)
|
|
24
28
|
.join(' / ');
|
|
29
|
+
|
|
30
|
+
return filtered === 'Default Title' ? '' : filtered;
|
|
25
31
|
}
|
|
26
|
-
|
|
32
|
+
|
|
33
|
+
const title = variant?.title || '';
|
|
34
|
+
return title === 'Default Title' ? '' : title;
|
|
27
35
|
};
|
|
28
36
|
|
|
29
37
|
export default function Cart({ resources = [] }: CartProps) {
|
|
@@ -39,8 +47,6 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
39
47
|
.filter((id) => !!id) as string[]
|
|
40
48
|
);
|
|
41
49
|
|
|
42
|
-
const hasServiceBoundProduct = boundServiceIds.size > 0;
|
|
43
|
-
|
|
44
50
|
const displayableItems = cartValues.filter(
|
|
45
51
|
(item) => !boundServiceIds.has(item.resourceId)
|
|
46
52
|
);
|
|
@@ -62,61 +68,57 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
62
68
|
});
|
|
63
69
|
|
|
64
70
|
const hasPhysicalProductWithPickup = cartValues.some(
|
|
65
|
-
(item) =>
|
|
71
|
+
(item) =>
|
|
72
|
+
item.variantIdPickup && item.variantIdPickup !== item.variantIdShipped
|
|
66
73
|
);
|
|
67
74
|
|
|
68
75
|
const canPickup = hasService && hasPhysicalProductWithPickup;
|
|
69
76
|
|
|
70
77
|
useEffect(() => {
|
|
71
|
-
if (
|
|
72
|
-
setPickupEnabled(true);
|
|
73
|
-
} else if (canPickup) {
|
|
78
|
+
if (canPickup) {
|
|
74
79
|
setPickupEnabled(true);
|
|
75
80
|
} else {
|
|
76
81
|
setPickupEnabled(false);
|
|
77
82
|
}
|
|
78
|
-
}, [canPickup
|
|
79
|
-
|
|
80
|
-
const isPickupMode =
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
queueUpdates.push({
|
|
89
|
-
resourceId: item.resourceId,
|
|
90
|
-
action,
|
|
91
|
-
variantId: item.variantId,
|
|
92
|
-
variantIdShipped: item.variantIdShipped,
|
|
93
|
-
variantIdPickup: item.variantIdPickup,
|
|
94
|
-
boundResourceId: item.boundResourceId,
|
|
95
|
-
suppressModal: action === 'add' ? true : undefined,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (item.boundResourceId) {
|
|
99
|
-
queueUpdates.push({
|
|
100
|
-
resourceId: item.boundResourceId,
|
|
83
|
+
}, [canPickup]);
|
|
84
|
+
|
|
85
|
+
const isPickupMode = canPickup && pickupEnabled;
|
|
86
|
+
|
|
87
|
+
const dispatchAction = (item: CartItemState, action: 'add' | 'remove') => {
|
|
88
|
+
addQueue.set([
|
|
89
|
+
...addQueue.get(),
|
|
90
|
+
{
|
|
91
|
+
resourceId: item.resourceId,
|
|
101
92
|
action,
|
|
93
|
+
variantId: item.variantId,
|
|
94
|
+
variantIdShipped: item.variantIdShipped,
|
|
95
|
+
variantIdPickup: item.variantIdPickup,
|
|
96
|
+
boundResourceId: item.boundResourceId,
|
|
102
97
|
suppressModal: action === 'add' ? true : undefined,
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
addQueue.set([...addQueue.get(), ...queueUpdates]);
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
107
100
|
};
|
|
108
101
|
|
|
102
|
+
if (isHandoff) {
|
|
103
|
+
return (
|
|
104
|
+
<div
|
|
105
|
+
className="fixed inset-0 flex flex-col items-center justify-center bg-black bg-opacity-75 backdrop-blur-md"
|
|
106
|
+
style={{ zIndex: 9005 }}
|
|
107
|
+
>
|
|
108
|
+
<div className="h-12 w-12 animate-spin rounded-full border-4 border-gray-200 border-t-white"></div>
|
|
109
|
+
<h3 className="mt-4 text-lg font-bold text-white">
|
|
110
|
+
Finalizing Handoff...
|
|
111
|
+
</h3>
|
|
112
|
+
<p className="mt-2 text-sm text-gray-300">
|
|
113
|
+
Redirecting to Shopify secured payment
|
|
114
|
+
</p>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
if (cartValues.length === 0) {
|
|
110
120
|
return (
|
|
111
121
|
<div className="relative">
|
|
112
|
-
{isHandoff && (
|
|
113
|
-
<div className="absolute inset-0 z-103 flex flex-col items-center justify-center rounded-lg bg-black bg-opacity-75 backdrop-blur-md">
|
|
114
|
-
<div className="h-12 w-12 animate-spin rounded-full border-4 border-gray-200 border-t-black"></div>
|
|
115
|
-
<h3 className="mt-4 text-lg font-bold text-gray-900">
|
|
116
|
-
Finalizing Handoff...
|
|
117
|
-
</h3>
|
|
118
|
-
</div>
|
|
119
|
-
)}
|
|
120
122
|
<div className="rounded-lg border bg-gray-50 p-8 text-center">
|
|
121
123
|
<h2 className="text-xl font-bold">Your cart is empty</h2>
|
|
122
124
|
<p className="mt-2 text-gray-600">Add some items to get started.</p>
|
|
@@ -164,6 +166,7 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
164
166
|
activeVariantIdFirst ||
|
|
165
167
|
firstItem.variantIdPickup ||
|
|
166
168
|
firstItem.variantId;
|
|
169
|
+
|
|
167
170
|
const { src, srcSet } = getShopifyImage(
|
|
168
171
|
resource,
|
|
169
172
|
'600',
|
|
@@ -202,16 +205,27 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
202
205
|
{resource.title}
|
|
203
206
|
</h3>
|
|
204
207
|
{isService && (
|
|
205
|
-
<span className="inline-flex items-center rounded-
|
|
208
|
+
<span className="inline-flex items-center rounded-sm bg-blue-50 px-2 py-0.5 text-xs font-bold text-blue-700">
|
|
206
209
|
{serviceDuration} mins
|
|
207
210
|
</span>
|
|
208
211
|
)}
|
|
209
212
|
</div>
|
|
213
|
+
|
|
210
214
|
{boundServiceResource && (
|
|
211
|
-
<
|
|
212
|
-
<span className="
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
<div className="mt-2 flex items-center gap-2 rounded-md bg-blue-50 px-3 py-2">
|
|
216
|
+
<span className="inline-block h-2 w-2 rounded-full bg-blue-500"></span>
|
|
217
|
+
<div>
|
|
218
|
+
<p className="text-sm font-bold text-blue-900">
|
|
219
|
+
Includes Booking: {boundServiceResource.title}
|
|
220
|
+
</p>
|
|
221
|
+
<p className="text-xs text-blue-700">
|
|
222
|
+
Duration:{' '}
|
|
223
|
+
{boundServiceResource.optionsPayload
|
|
224
|
+
?.bookingLengthMinutes || 0}{' '}
|
|
225
|
+
mins
|
|
226
|
+
</p>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
215
229
|
)}
|
|
216
230
|
<p className="mt-1 text-sm text-gray-500">
|
|
217
231
|
{resource.oneliner}
|
|
@@ -220,7 +234,7 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
220
234
|
</div>
|
|
221
235
|
|
|
222
236
|
<div className="mt-4 space-y-4 border-t border-gray-100 pt-4">
|
|
223
|
-
{items.map((item) => {
|
|
237
|
+
{items.map((item, idx) => {
|
|
224
238
|
const activeVariantId = isPickupMode
|
|
225
239
|
? item.variantIdPickup
|
|
226
240
|
: item.variantIdShipped;
|
|
@@ -242,30 +256,31 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
242
256
|
price = variant.price?.amount || '0.00';
|
|
243
257
|
currency = variant.price?.currencyCode || 'USD';
|
|
244
258
|
variantTitle = getCleanVariantTitle(variant);
|
|
245
|
-
} else if (variants.length > 0 && !variantTitle) {
|
|
246
|
-
const v = variants[0];
|
|
247
|
-
price = v.price?.amount || '0.00';
|
|
248
|
-
currency = v.price?.currencyCode || 'USD';
|
|
249
|
-
variantTitle = getCleanVariantTitle(v);
|
|
250
259
|
}
|
|
251
260
|
|
|
252
261
|
return (
|
|
253
262
|
<div
|
|
254
|
-
key={`${item.resourceId}_${displayId}`}
|
|
263
|
+
key={`${item.resourceId}_${displayId}_${idx}`}
|
|
255
264
|
className="flex items-center justify-between"
|
|
256
265
|
>
|
|
257
266
|
<div className="flex items-center gap-2">
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
variantTitle
|
|
261
|
-
|
|
262
|
-
)}
|
|
263
|
-
</div>
|
|
264
|
-
{isPickupMode && !isService && (
|
|
265
|
-
<span className="inline-flex items-center rounded bg-gray-100 px-2 py-0.5 text-xs font-bold text-gray-800">
|
|
266
|
-
Store Pickup
|
|
267
|
-
</span>
|
|
267
|
+
{variantTitle && (
|
|
268
|
+
<div className="text-sm font-bold text-gray-700">
|
|
269
|
+
<span>{variantTitle}</span>
|
|
270
|
+
</div>
|
|
268
271
|
)}
|
|
272
|
+
{isPickupMode &&
|
|
273
|
+
!isService &&
|
|
274
|
+
(item.variantIdPickup &&
|
|
275
|
+
item.variantIdPickup !== item.variantIdShipped ? (
|
|
276
|
+
<span className="inline-flex items-center rounded bg-gray-100 px-2 py-0.5 text-xs font-bold text-gray-800">
|
|
277
|
+
Store Pickup
|
|
278
|
+
</span>
|
|
279
|
+
) : (
|
|
280
|
+
<span className="inline-flex items-center rounded bg-red-50 px-2 py-0.5 text-xs font-bold text-red-700">
|
|
281
|
+
Not available for pickup
|
|
282
|
+
</span>
|
|
283
|
+
))}
|
|
269
284
|
</div>
|
|
270
285
|
|
|
271
286
|
<div className="flex items-center">
|
|
@@ -296,9 +311,7 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
296
311
|
) : (
|
|
297
312
|
<div className="flex items-center rounded-md border border-gray-300">
|
|
298
313
|
<button
|
|
299
|
-
onClick={() =>
|
|
300
|
-
dispatchDualAction(item, 'remove')
|
|
301
|
-
}
|
|
314
|
+
onClick={() => dispatchAction(item, 'remove')}
|
|
302
315
|
className="px-3 py-1 text-gray-600 hover:bg-gray-100"
|
|
303
316
|
>
|
|
304
317
|
-
|
|
@@ -307,9 +320,7 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
307
320
|
{item.quantity}
|
|
308
321
|
</span>
|
|
309
322
|
<button
|
|
310
|
-
onClick={() =>
|
|
311
|
-
dispatchDualAction(item, 'add')
|
|
312
|
-
}
|
|
323
|
+
onClick={() => dispatchAction(item, 'add')}
|
|
313
324
|
className="px-3 py-1 text-gray-600 hover:bg-gray-100"
|
|
314
325
|
>
|
|
315
326
|
+
|
|
@@ -333,6 +344,22 @@ export default function Cart({ resources = [] }: CartProps) {
|
|
|
333
344
|
<button
|
|
334
345
|
className="rounded-lg bg-black px-6 py-3 font-bold text-white transition-colors hover:bg-gray-800"
|
|
335
346
|
onClick={() => {
|
|
347
|
+
const currentCart = cartStore.get();
|
|
348
|
+
const sanitizedCart = { ...currentCart };
|
|
349
|
+
|
|
350
|
+
Object.keys(sanitizedCart).forEach((key) => {
|
|
351
|
+
const item = sanitizedCart[key];
|
|
352
|
+
|
|
353
|
+
if (isPickupMode && item.variantIdPickup) {
|
|
354
|
+
item.variantId = item.variantIdPickup;
|
|
355
|
+
} else if (!isPickupMode && item.variantIdShipped) {
|
|
356
|
+
item.variantId = item.variantIdShipped;
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
cartStore.set(sanitizedCart);
|
|
361
|
+
transactionTraceId.set(ulid());
|
|
362
|
+
|
|
336
363
|
cartState.set(CART_STATES.CHECKOUT);
|
|
337
364
|
}}
|
|
338
365
|
>
|