astro-tractstack 2.3.0 → 2.3.2
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 +130 -19
- package/package.json +2 -2
- package/templates/custom/minimal/CodeHook.astro +10 -2
- package/templates/custom/shopify/Cart.tsx +115 -77
- package/templates/custom/shopify/CheckoutModal.tsx +509 -120
- package/templates/custom/shopify/NativeBookingCalendar.tsx +375 -0
- package/templates/custom/shopify/ShopifyCartManager.tsx +91 -45
- package/templates/custom/shopify/ShopifyCheckout.tsx +4 -33
- package/templates/custom/shopify/ShopifyProductGrid.tsx +170 -176
- package/templates/custom/shopify/ShopifyServiceList.tsx +112 -51
- package/templates/custom/with-examples/CodeHook.astro +10 -2
- package/templates/src/components/Footer.astro +6 -6
- package/templates/src/components/Header.astro +23 -11
- package/templates/src/components/Menu.tsx +157 -135
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +27 -6
- package/templates/src/components/codehooks/EpinetTableView.tsx +153 -112
- package/templates/src/components/codehooks/EpinetWrapper.tsx +4 -1
- package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +8 -1
- package/templates/src/components/codehooks/ProductCardSetup.tsx +9 -1
- package/templates/src/components/codehooks/ProductGridSetup.tsx +9 -1
- package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +2 -1
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +1 -1
- package/templates/src/components/edit/ToolBar.tsx +2 -1
- package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +2 -2
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +13 -0
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +3 -3
- package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +2 -2
- package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +2 -2
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
- 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/edit/state/SaveModal.tsx +1 -1
- package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +8 -3
- package/templates/src/components/form/DateTimeInput.tsx +10 -3
- package/templates/src/components/form/FileUpload.tsx +11 -5
- package/templates/src/components/form/NumberInput.tsx +2 -2
- package/templates/src/components/form/advanced/APIConfigSection.tsx +208 -2
- package/templates/src/components/form/brand/SiteConfigSection.tsx +10 -0
- 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 +252 -110
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +2 -2
- 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 +88 -56
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +14 -4
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +14 -5
- package/templates/src/components/storykeep/email-builder/Blocks.tsx +169 -0
- package/templates/src/components/storykeep/email-builder/EmailBuilder.tsx +223 -0
- package/templates/src/components/storykeep/email-builder/PreviewModal.tsx +136 -0
- package/templates/src/components/storykeep/email-builder/PropertyPanel.tsx +154 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +104 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +419 -0
- package/templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx +105 -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/layouts/Layout.astro +8 -5
- 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 +97 -25
- package/templates/src/types/formTypes.ts +4 -2
- package/templates/src/types/tractstack.ts +59 -2
- 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/brandConfig.ts +2 -0
- package/templates/src/utils/api/brandHelpers.ts +26 -0
- package/templates/src/utils/api/emailHelpers.ts +105 -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 +3 -2
- package/utils/inject-files.ts +116 -5
- package/templates/custom/shopify/CalDotComBooking.tsx +0 -44
|
@@ -4,16 +4,15 @@ import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
|
4
4
|
import ClipboardDocumentIcon from '@heroicons/react/24/outline/ClipboardDocumentIcon';
|
|
5
5
|
import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
|
|
6
6
|
import {
|
|
7
|
-
shopifyData,
|
|
8
7
|
shopifyStatus,
|
|
9
|
-
|
|
8
|
+
shopifyActiveTabStore,
|
|
10
9
|
type ShopifyProduct,
|
|
11
10
|
} from '@/stores/shopify';
|
|
12
|
-
import ProductTable from './controls/content/ProductTable';
|
|
13
11
|
import ResourceForm from './controls/content/ResourceForm';
|
|
14
12
|
import { saveBrandConfigWithStateUpdate } from '@/utils/api/brandConfig';
|
|
15
13
|
import {
|
|
16
14
|
deleteResource,
|
|
15
|
+
getResource,
|
|
17
16
|
getResourcesByCategory,
|
|
18
17
|
} from '@/utils/api/resourceConfig';
|
|
19
18
|
import { convertToLocalState } from '@/utils/api/brandHelpers';
|
|
@@ -21,6 +20,13 @@ import BooleanToggle from '@/components/form/BooleanToggle';
|
|
|
21
20
|
import type { BrandConfig, BrandConfigState } from '@/types/tractstack';
|
|
22
21
|
import type { ResourceNode } from '@/types/compositorTypes';
|
|
23
22
|
import type { ResourceConfig } from '@/types/tractstack';
|
|
23
|
+
import ShopifyDashboard from './shopify/ShopifyDashboard';
|
|
24
|
+
import ShopifyDashboard_Products from './shopify/ShopifyDashboard_Products';
|
|
25
|
+
import ShopifyDashboard_Services from './shopify/ShopifyDashboard_Services';
|
|
26
|
+
import ShopifyDashboard_Schedule from './shopify/ShopifyDashboard_Schedule';
|
|
27
|
+
import ShopifyDashboard_Search from './shopify/ShopifyDashboard_Search';
|
|
28
|
+
import ShopifyDashboard_Bookings from './shopify/ShopifyDashboard_Bookings';
|
|
29
|
+
import ShopifyDashboard_Emails from './shopify/ShopifyDashboard_Emails';
|
|
24
30
|
|
|
25
31
|
interface DashboardShopifyProps {
|
|
26
32
|
brandConfig: BrandConfig;
|
|
@@ -33,8 +39,9 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
33
39
|
brandConfig,
|
|
34
40
|
existingResources,
|
|
35
41
|
}: DashboardShopifyProps) {
|
|
36
|
-
const data = useStore(shopifyData);
|
|
37
42
|
const status = useStore(shopifyStatus);
|
|
43
|
+
const activeTab = useStore(shopifyActiveTabStore);
|
|
44
|
+
|
|
38
45
|
const [selectedProduct, setSelectedProduct] = useState<ShopifyProduct | null>(
|
|
39
46
|
null
|
|
40
47
|
);
|
|
@@ -51,6 +58,12 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
51
58
|
);
|
|
52
59
|
const [showTypeSelector, setShowTypeSelector] = useState(false);
|
|
53
60
|
|
|
61
|
+
const [showSmartCartWarning, setShowSmartCartWarning] = useState(false);
|
|
62
|
+
const [pendingImport, setPendingImport] = useState<{
|
|
63
|
+
category: string;
|
|
64
|
+
product: ShopifyProduct;
|
|
65
|
+
} | null>(null);
|
|
66
|
+
|
|
54
67
|
const [machineState, setMachineState] = useState<MachineState>('INIT');
|
|
55
68
|
const [internalBrandConfig, setInternalBrandConfig] =
|
|
56
69
|
useState<BrandConfigState | null>(null);
|
|
@@ -59,6 +72,17 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
59
72
|
const [wantService, setWantService] = useState(true);
|
|
60
73
|
const [isSaving, setIsSaving] = useState(false);
|
|
61
74
|
|
|
75
|
+
// Tab definitions
|
|
76
|
+
const tabs = [
|
|
77
|
+
{ id: 'dashboards', name: 'Dashboard' },
|
|
78
|
+
{ id: 'bookings', name: 'Bookings' },
|
|
79
|
+
{ id: 'products', name: 'Products' },
|
|
80
|
+
{ id: 'services', name: 'Services' },
|
|
81
|
+
{ id: 'schedule', name: 'Schedule' },
|
|
82
|
+
{ id: 'search', name: 'Import Products' },
|
|
83
|
+
{ id: 'emails', name: 'Emails' },
|
|
84
|
+
];
|
|
85
|
+
|
|
62
86
|
useEffect(() => {
|
|
63
87
|
if (brandConfig) {
|
|
64
88
|
const localState = convertToLocalState(brandConfig);
|
|
@@ -73,14 +97,6 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
73
97
|
}
|
|
74
98
|
}, [brandConfig]);
|
|
75
99
|
|
|
76
|
-
// Operational Effect: Fetch products only when READY
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
if (machineState === 'READY') {
|
|
79
|
-
fetchShopifyProducts();
|
|
80
|
-
}
|
|
81
|
-
}, [machineState]);
|
|
82
|
-
|
|
83
|
-
// Memoize the lookup map for performance (gid -> ResourceNode)
|
|
84
100
|
const linkedResourceMap = useMemo(() => {
|
|
85
101
|
const map = new Map<string, ResourceNode>();
|
|
86
102
|
resources.forEach((r) => {
|
|
@@ -91,14 +107,9 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
91
107
|
return map;
|
|
92
108
|
}, [resources]);
|
|
93
109
|
|
|
94
|
-
const handleRefresh = () => {
|
|
95
|
-
fetchShopifyProducts();
|
|
96
|
-
};
|
|
97
|
-
|
|
98
110
|
const refreshResources = async () => {
|
|
99
111
|
const tenantId = window.TRACTSTACK_CONFIG?.tenantId || 'default';
|
|
100
112
|
try {
|
|
101
|
-
// Fetch both categories if configured, then merge unique
|
|
102
113
|
const promises = [];
|
|
103
114
|
if (internalBrandConfig?.knownResources['product']) {
|
|
104
115
|
promises.push(getResourcesByCategory(tenantId, 'product'));
|
|
@@ -129,18 +140,48 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
129
140
|
setTargetProduct(product);
|
|
130
141
|
setShowTypeSelector(true);
|
|
131
142
|
} else if (hasServiceSchema) {
|
|
132
|
-
|
|
143
|
+
executePreFlightCheck('service', product);
|
|
133
144
|
} else {
|
|
134
|
-
|
|
145
|
+
executePreFlightCheck('product', product);
|
|
135
146
|
}
|
|
136
147
|
};
|
|
137
148
|
|
|
138
|
-
|
|
149
|
+
// Handler for ProductTable (External Catalog)
|
|
150
|
+
const handleEditFromCatalog = (
|
|
151
|
+
_product: ShopifyProduct,
|
|
152
|
+
resource: ResourceNode
|
|
153
|
+
) => {
|
|
139
154
|
setDraftResource(resource as any);
|
|
140
155
|
setIsCreateMode(false);
|
|
141
156
|
setShowResourceModal(true);
|
|
142
157
|
};
|
|
143
158
|
|
|
159
|
+
// Handler for ResourceTable (Local Management)
|
|
160
|
+
const handleEditResource = async (resourceId: string) => {
|
|
161
|
+
try {
|
|
162
|
+
const resource = await getResource(
|
|
163
|
+
window.TRACTSTACK_CONFIG?.tenantId || 'default',
|
|
164
|
+
resourceId
|
|
165
|
+
);
|
|
166
|
+
setDraftResource(resource as any);
|
|
167
|
+
setIsCreateMode(false);
|
|
168
|
+
setShowResourceModal(true);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Failed to load resource for editing:', error);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const executePreFlightCheck = (category: string, product: ShopifyProduct) => {
|
|
175
|
+
const hasMode = product.options.some((opt) => opt.name === 'Mode');
|
|
176
|
+
if (category === 'service' || hasMode) {
|
|
177
|
+
startCreateFlow(category, product);
|
|
178
|
+
} else {
|
|
179
|
+
setPendingImport({ category, product });
|
|
180
|
+
setShowSmartCartWarning(true);
|
|
181
|
+
setShowTypeSelector(false);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
144
185
|
const startCreateFlow = (category: string, product: ShopifyProduct) => {
|
|
145
186
|
const schema = internalBrandConfig?.knownResources[category] || {};
|
|
146
187
|
const mergedOptions: Record<string, any> = {
|
|
@@ -148,7 +189,6 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
148
189
|
shopifyData: JSON.stringify(product),
|
|
149
190
|
};
|
|
150
191
|
|
|
151
|
-
// Apply schema defaults for missing fields
|
|
152
192
|
Object.entries(schema).forEach(([key, def]) => {
|
|
153
193
|
if (mergedOptions[key] === undefined) {
|
|
154
194
|
if (def.type === 'number') {
|
|
@@ -190,7 +230,6 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
190
230
|
window.TRACTSTACK_CONFIG?.tenantId || 'default',
|
|
191
231
|
resourceId
|
|
192
232
|
);
|
|
193
|
-
// Optimistic update
|
|
194
233
|
setResources((prev) => prev.filter((r) => r.id !== resourceId));
|
|
195
234
|
} catch (error) {
|
|
196
235
|
console.error('Unlink failed', error);
|
|
@@ -214,11 +253,11 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
214
253
|
try {
|
|
215
254
|
const updatedKnownResources = { ...internalBrandConfig.knownResources };
|
|
216
255
|
|
|
217
|
-
// 1. Product Schema
|
|
218
256
|
if (wantProduct) {
|
|
219
257
|
updatedKnownResources['product'] = {
|
|
220
258
|
gid: { type: 'string', optional: false },
|
|
221
259
|
allowMultiple: { type: 'boolean', optional: false },
|
|
260
|
+
group: { type: 'string', optional: true },
|
|
222
261
|
shopifyData: { type: 'string', optional: false },
|
|
223
262
|
shopifyImage: { type: 'string', optional: true, defaultValue: '{}' },
|
|
224
263
|
...(wantService
|
|
@@ -233,10 +272,10 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
233
272
|
};
|
|
234
273
|
}
|
|
235
274
|
|
|
236
|
-
// 2. Service Schema
|
|
237
275
|
if (wantService) {
|
|
238
276
|
updatedKnownResources['service'] = {
|
|
239
277
|
gid: { type: 'string', optional: true },
|
|
278
|
+
group: { type: 'string', optional: true },
|
|
240
279
|
shopifyData: { type: 'string', optional: true },
|
|
241
280
|
shopifyImage: { type: 'string', optional: true, defaultValue: '{}' },
|
|
242
281
|
bookingLengthMinutes: {
|
|
@@ -269,11 +308,25 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
269
308
|
}
|
|
270
309
|
};
|
|
271
310
|
|
|
272
|
-
|
|
273
|
-
return
|
|
274
|
-
|
|
311
|
+
const handleDismissHelper = async () => {
|
|
312
|
+
if (!internalBrandConfig) return;
|
|
313
|
+
try {
|
|
314
|
+
const updatedState = {
|
|
315
|
+
...internalBrandConfig,
|
|
316
|
+
showShopifyHelper: false,
|
|
317
|
+
};
|
|
318
|
+
await saveBrandConfigWithStateUpdate(
|
|
319
|
+
window.TRACTSTACK_CONFIG?.tenantId || 'default',
|
|
320
|
+
updatedState
|
|
321
|
+
);
|
|
322
|
+
setInternalBrandConfig(updatedState);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error('Failed to dismiss Shopify helper:', error);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (machineState === 'INIT') return null;
|
|
275
329
|
|
|
276
|
-
// CONFIG State
|
|
277
330
|
if (machineState === 'CONFIG' || machineState === 'UPDATE') {
|
|
278
331
|
return (
|
|
279
332
|
<div className="mt-8 rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
|
|
@@ -336,7 +389,6 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
336
389
|
);
|
|
337
390
|
}
|
|
338
391
|
|
|
339
|
-
// READY State
|
|
340
392
|
return (
|
|
341
393
|
<div className="mt-8 rounded-lg border border-gray-200 bg-white p-6 shadow-sm">
|
|
342
394
|
<div className="mb-6 border-b border-gray-100 pb-4">
|
|
@@ -346,12 +398,66 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
346
398
|
</p>
|
|
347
399
|
</div>
|
|
348
400
|
|
|
349
|
-
{
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
401
|
+
{/* Persistent Onboarding Helper Banner */}
|
|
402
|
+
{internalBrandConfig?.showShopifyHelper && (
|
|
403
|
+
<div className="relative mb-8 rounded-lg border border-cyan-200 bg-cyan-50 p-6 pr-12 shadow-sm">
|
|
404
|
+
<button
|
|
405
|
+
onClick={handleDismissHelper}
|
|
406
|
+
className="absolute right-4 top-4 text-cyan-600 hover:text-cyan-800"
|
|
407
|
+
title="Dismiss instructions"
|
|
408
|
+
>
|
|
409
|
+
<XMarkIcon className="h-6 w-6" />
|
|
410
|
+
</button>
|
|
411
|
+
<h3 className="text-lg font-bold text-cyan-900">
|
|
412
|
+
Smart Cart Architecture: "Mode" Options
|
|
413
|
+
</h3>
|
|
414
|
+
<div className="mt-2 space-y-3 text-sm text-cyan-800">
|
|
415
|
+
<p>
|
|
416
|
+
To enable automatic shipping fee bypass for local pickup, your
|
|
417
|
+
Shopify products must be configured with a specific architectural
|
|
418
|
+
pattern:
|
|
419
|
+
</p>
|
|
420
|
+
<ul className="list-inside list-disc space-y-1 font-bold">
|
|
421
|
+
<li>
|
|
422
|
+
Option Name: Must be exactly{' '}
|
|
423
|
+
<code className="rounded bg-cyan-100 px-1">Mode</code>
|
|
424
|
+
</li>
|
|
425
|
+
<li>
|
|
426
|
+
Option Values: Must include{' '}
|
|
427
|
+
<code className="rounded bg-cyan-100 px-1">Shipped</code> and{' '}
|
|
428
|
+
<code className="rounded bg-cyan-100 px-1">Pickup</code>
|
|
429
|
+
</li>
|
|
430
|
+
</ul>
|
|
431
|
+
<p>
|
|
432
|
+
Without this "Mode" option, items will always be treated as
|
|
433
|
+
standard shipped products.
|
|
434
|
+
</p>
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
)}
|
|
438
|
+
|
|
439
|
+
{/* Tab Navigation Shell */}
|
|
440
|
+
<div className="mb-8 border-b border-gray-200">
|
|
441
|
+
<nav className="-mb-px flex flex-wrap gap-x-8" aria-label="Tabs">
|
|
442
|
+
{tabs.map((tab) => (
|
|
443
|
+
<button
|
|
444
|
+
key={tab.id}
|
|
445
|
+
onClick={() => shopifyActiveTabStore.set(tab.id)}
|
|
446
|
+
className={`whitespace-nowrap border-b-2 px-1 py-4 text-sm font-bold ${
|
|
447
|
+
activeTab === tab.id
|
|
448
|
+
? 'border-cyan-500 text-cyan-600'
|
|
449
|
+
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'
|
|
450
|
+
} `}
|
|
451
|
+
>
|
|
452
|
+
{tab.name}
|
|
453
|
+
</button>
|
|
454
|
+
))}
|
|
455
|
+
</nav>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<div className="space-y-6">
|
|
459
|
+
{status.error && (
|
|
460
|
+
<div className="mb-6 rounded-md bg-red-50 p-4">
|
|
355
461
|
<div className="ml-3">
|
|
356
462
|
<h3 className="text-sm font-bold text-red-800">Error</h3>
|
|
357
463
|
<div className="mt-2 text-sm text-red-700">
|
|
@@ -359,32 +465,50 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
359
465
|
</div>
|
|
360
466
|
</div>
|
|
361
467
|
</div>
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
<
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
468
|
+
)}
|
|
469
|
+
|
|
470
|
+
{activeTab === 'dashboards' && <ShopifyDashboard />}
|
|
471
|
+
|
|
472
|
+
{activeTab === 'bookings' && (
|
|
473
|
+
<ShopifyDashboard_Bookings existingResources={resources} />
|
|
474
|
+
)}
|
|
475
|
+
|
|
476
|
+
{activeTab === 'products' && (
|
|
477
|
+
<ShopifyDashboard_Products
|
|
478
|
+
resources={resources}
|
|
479
|
+
onEdit={handleEditResource}
|
|
480
|
+
onCreate={() => shopifyActiveTabStore.set('search')}
|
|
481
|
+
onRefresh={refreshResources}
|
|
482
|
+
/>
|
|
483
|
+
)}
|
|
484
|
+
|
|
485
|
+
{activeTab === 'services' && (
|
|
486
|
+
<ShopifyDashboard_Services
|
|
487
|
+
resources={resources}
|
|
488
|
+
onEdit={handleEditResource}
|
|
489
|
+
onCreate={() => shopifyActiveTabStore.set('search')}
|
|
490
|
+
onRefresh={refreshResources}
|
|
491
|
+
/>
|
|
492
|
+
)}
|
|
493
|
+
|
|
494
|
+
{activeTab === 'schedule' && (
|
|
495
|
+
<ShopifyDashboard_Schedule brandConfig={brandConfig} />
|
|
496
|
+
)}
|
|
497
|
+
|
|
498
|
+
{activeTab === 'search' && (
|
|
499
|
+
<ShopifyDashboard_Search
|
|
500
|
+
linkedResourceMap={linkedResourceMap}
|
|
501
|
+
onSelectProduct={setSelectedProduct}
|
|
502
|
+
onLink={handleLink}
|
|
503
|
+
onUnlink={handleUnlink}
|
|
504
|
+
onEdit={handleEditFromCatalog}
|
|
505
|
+
/>
|
|
506
|
+
)}
|
|
507
|
+
|
|
508
|
+
{activeTab === 'emails' && <ShopifyDashboard_Emails />}
|
|
509
|
+
</div>
|
|
386
510
|
|
|
387
|
-
{/*
|
|
511
|
+
{/* Shared Modals */}
|
|
388
512
|
{selectedProduct && (
|
|
389
513
|
<div className="relative z-50" aria-modal="true">
|
|
390
514
|
<div className="fixed inset-0 bg-black bg-opacity-75" />
|
|
@@ -399,59 +523,41 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
399
523
|
</h3>
|
|
400
524
|
<button
|
|
401
525
|
type="button"
|
|
402
|
-
className="rounded-md bg-white text-gray-400 hover:text-gray-
|
|
526
|
+
className="rounded-md bg-white text-gray-400 hover:text-gray-50"
|
|
403
527
|
onClick={() => setSelectedProduct(null)}
|
|
404
528
|
>
|
|
405
529
|
<XMarkIcon className="h-6 w-6" />
|
|
406
530
|
</button>
|
|
407
531
|
</div>
|
|
408
532
|
<div className="overflow-y-auto p-6">
|
|
409
|
-
<div className="mb-
|
|
410
|
-
<h4 className="
|
|
411
|
-
|
|
533
|
+
<div className="mb-2 flex items-center justify-between">
|
|
534
|
+
<h4 className="text-sm font-bold text-gray-900">
|
|
535
|
+
Raw API Data
|
|
412
536
|
</h4>
|
|
413
|
-
<
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
Handle
|
|
417
|
-
</dt>
|
|
418
|
-
<dd className="mt-1 text-sm text-gray-900">
|
|
419
|
-
{selectedProduct.handle}
|
|
420
|
-
</dd>
|
|
421
|
-
</div>
|
|
422
|
-
</dl>
|
|
423
|
-
</div>
|
|
424
|
-
<div>
|
|
425
|
-
<div className="mb-2 flex items-center justify-between">
|
|
426
|
-
<h4 className="text-sm font-bold text-gray-900">
|
|
427
|
-
Raw API Data
|
|
428
|
-
</h4>
|
|
429
|
-
<button
|
|
430
|
-
onClick={handleCopy}
|
|
431
|
-
className="flex items-center gap-1 text-xs text-cyan-600 hover:text-cyan-800"
|
|
432
|
-
>
|
|
433
|
-
{copied ? (
|
|
434
|
-
<CheckIcon className="h-4 w-4" />
|
|
435
|
-
) : (
|
|
436
|
-
<ClipboardDocumentIcon className="h-4 w-4" />
|
|
437
|
-
)}
|
|
438
|
-
{copied ? 'Copied' : 'Copy JSON'}
|
|
439
|
-
</button>
|
|
440
|
-
</div>
|
|
441
|
-
<pre
|
|
442
|
-
className="overflow-auto rounded-md bg-gray-50 p-4 text-xs text-gray-800"
|
|
443
|
-
style={{ maxHeight: '40vh' }}
|
|
537
|
+
<button
|
|
538
|
+
onClick={handleCopy}
|
|
539
|
+
className="flex items-center gap-1 text-xs text-cyan-600 hover:text-cyan-800"
|
|
444
540
|
>
|
|
445
|
-
{
|
|
446
|
-
|
|
541
|
+
{copied ? (
|
|
542
|
+
<CheckIcon className="h-4 w-4" />
|
|
543
|
+
) : (
|
|
544
|
+
<ClipboardDocumentIcon className="h-4 w-4" />
|
|
545
|
+
)}
|
|
546
|
+
{copied ? 'Copied' : 'Copy JSON'}
|
|
547
|
+
</button>
|
|
447
548
|
</div>
|
|
549
|
+
<pre
|
|
550
|
+
className="overflow-auto rounded-md bg-gray-50 p-4 text-xs text-gray-800"
|
|
551
|
+
style={{ maxHeight: '40vh' }}
|
|
552
|
+
>
|
|
553
|
+
{JSON.stringify(selectedProduct, null, 2)}
|
|
554
|
+
</pre>
|
|
448
555
|
</div>
|
|
449
556
|
</div>
|
|
450
557
|
</div>
|
|
451
558
|
</div>
|
|
452
559
|
)}
|
|
453
560
|
|
|
454
|
-
{/* Type Selector Modal */}
|
|
455
561
|
{showTypeSelector && targetProduct && (
|
|
456
562
|
<div className="relative z-50" aria-modal="true">
|
|
457
563
|
<div className="fixed inset-0 bg-black bg-opacity-50" />
|
|
@@ -462,27 +568,66 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
462
568
|
Import as...
|
|
463
569
|
</h3>
|
|
464
570
|
<p className="mt-2 text-sm text-gray-500">
|
|
465
|
-
Should "{targetProduct.title}" be
|
|
466
|
-
Service?
|
|
571
|
+
Should "{targetProduct.title}" be a Product or Service?
|
|
467
572
|
</p>
|
|
468
573
|
<div className="mt-6 flex flex-col gap-3">
|
|
469
574
|
<button
|
|
470
|
-
onClick={() =>
|
|
575
|
+
onClick={() =>
|
|
576
|
+
executePreFlightCheck('product', targetProduct)
|
|
577
|
+
}
|
|
471
578
|
className="flex w-full items-center justify-center rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-500"
|
|
472
579
|
>
|
|
473
580
|
Product
|
|
474
581
|
</button>
|
|
475
582
|
<button
|
|
476
|
-
onClick={() =>
|
|
583
|
+
onClick={() =>
|
|
584
|
+
executePreFlightCheck('service', targetProduct)
|
|
585
|
+
}
|
|
477
586
|
className="flex w-full items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50"
|
|
478
587
|
>
|
|
479
588
|
Service (Bookable)
|
|
480
589
|
</button>
|
|
481
590
|
</div>
|
|
482
|
-
|
|
591
|
+
</div>
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
</div>
|
|
595
|
+
)}
|
|
596
|
+
|
|
597
|
+
{showSmartCartWarning && pendingImport && (
|
|
598
|
+
<div className="relative z-50" aria-modal="true">
|
|
599
|
+
<div className="fixed inset-0 bg-black bg-opacity-50" />
|
|
600
|
+
<div className="fixed inset-0 flex items-center justify-center p-4">
|
|
601
|
+
<div className="w-full max-w-md overflow-hidden rounded-lg bg-white shadow-xl">
|
|
602
|
+
<div className="px-6 py-4">
|
|
603
|
+
<h3 className="text-lg font-bold text-gray-900">
|
|
604
|
+
Missing Smart Cart Architecture
|
|
605
|
+
</h3>
|
|
606
|
+
<p className="mt-2 text-sm text-gray-500">
|
|
607
|
+
To enable Smart Cart pickup, the product must have a "Mode"
|
|
608
|
+
option containing "Shipped" and "Pickup". If you import now,
|
|
609
|
+
it will be standard shipping only.
|
|
610
|
+
</p>
|
|
611
|
+
<div className="mt-6 flex flex-col gap-3">
|
|
483
612
|
<button
|
|
484
|
-
onClick={() =>
|
|
485
|
-
|
|
613
|
+
onClick={() => {
|
|
614
|
+
setShowSmartCartWarning(false);
|
|
615
|
+
startCreateFlow(
|
|
616
|
+
pendingImport.category,
|
|
617
|
+
pendingImport.product
|
|
618
|
+
);
|
|
619
|
+
setPendingImport(null);
|
|
620
|
+
}}
|
|
621
|
+
className="flex w-full items-center justify-center rounded-md bg-cyan-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-500"
|
|
622
|
+
>
|
|
623
|
+
Acknowledge & Import
|
|
624
|
+
</button>
|
|
625
|
+
<button
|
|
626
|
+
onClick={() => {
|
|
627
|
+
setShowSmartCartWarning(false);
|
|
628
|
+
setPendingImport(null);
|
|
629
|
+
}}
|
|
630
|
+
className="flex w-full items-center justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-bold text-gray-700 shadow-sm hover:bg-gray-50"
|
|
486
631
|
>
|
|
487
632
|
Cancel
|
|
488
633
|
</button>
|
|
@@ -493,14 +638,13 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
493
638
|
</div>
|
|
494
639
|
)}
|
|
495
640
|
|
|
496
|
-
{/* Resource Form Modal */}
|
|
497
641
|
{showResourceModal && draftResource && (
|
|
498
642
|
<div className="fixed inset-0 z-50 overflow-y-auto bg-gray-900 bg-opacity-50 backdrop-blur-sm">
|
|
499
643
|
<div className="flex min-h-full items-center justify-center p-4">
|
|
500
644
|
<div className="w-full max-w-2xl rounded-lg bg-white p-6 shadow-xl">
|
|
501
645
|
<ResourceForm
|
|
502
646
|
resourceData={draftResource as any}
|
|
503
|
-
fullContentMap={resources as any}
|
|
647
|
+
fullContentMap={resources as any}
|
|
504
648
|
categorySlug={draftResource.categorySlug || ''}
|
|
505
649
|
categorySchema={
|
|
506
650
|
internalBrandConfig?.knownResources[
|
|
@@ -511,9 +655,7 @@ export default function StoryKeepDashboard_Shopify({
|
|
|
511
655
|
onClose={(saved) => {
|
|
512
656
|
setShowResourceModal(false);
|
|
513
657
|
setIsCreateMode(true);
|
|
514
|
-
if (saved)
|
|
515
|
-
refreshResources();
|
|
516
|
-
}
|
|
658
|
+
if (saved) refreshResources();
|
|
517
659
|
}}
|
|
518
660
|
/>
|
|
519
661
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
1
|
+
import { useState, useEffect, type KeyboardEvent } from 'react';
|
|
2
2
|
import { useStore } from '@nanostores/react';
|
|
3
3
|
import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
|
|
4
4
|
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
|
|
@@ -125,7 +125,7 @@ export default function BeliefForm({
|
|
|
125
125
|
}
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
-
const handleKeyDown = (e:
|
|
128
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
129
129
|
if (e.key === 'Enter') {
|
|
130
130
|
e.preventDefault();
|
|
131
131
|
handleAddCustomValue();
|
|
@@ -170,7 +170,7 @@ export default function BeliefTable({
|
|
|
170
170
|
</div>
|
|
171
171
|
|
|
172
172
|
{/* Table Container */}
|
|
173
|
-
<div className="overflow-
|
|
173
|
+
<div className="overflow-x-auto rounded-lg border border-gray-200 bg-white shadow">
|
|
174
174
|
{filteredBeliefs.length === 0 ? (
|
|
175
175
|
<div className="px-6 py-12 text-center">
|
|
176
176
|
<svg
|
|
@@ -204,7 +204,7 @@ export default function BeliefTable({
|
|
|
204
204
|
)}
|
|
205
205
|
</div>
|
|
206
206
|
) : (
|
|
207
|
-
<div className="
|
|
207
|
+
<div className="inline-block min-w-full align-middle">
|
|
208
208
|
<table className="min-w-full divide-y divide-gray-200">
|
|
209
209
|
<thead className="bg-gray-50">
|
|
210
210
|
<tr>
|
|
@@ -237,15 +237,24 @@ export default function BeliefTable({
|
|
|
237
237
|
<tr key={belief.id} className="hover:bg-gray-50">
|
|
238
238
|
<td className="px-3 py-4 md:px-6">
|
|
239
239
|
<div className="flex flex-col">
|
|
240
|
-
<div
|
|
240
|
+
<div
|
|
241
|
+
className="max-w-xs truncate text-sm font-bold text-gray-900"
|
|
242
|
+
title={belief.title}
|
|
243
|
+
>
|
|
241
244
|
{belief.title}
|
|
242
245
|
</div>
|
|
243
|
-
<div
|
|
246
|
+
<div
|
|
247
|
+
className="max-w-xs truncate text-sm text-gray-500 md:hidden"
|
|
248
|
+
title={belief.slug}
|
|
249
|
+
>
|
|
244
250
|
{belief.slug}
|
|
245
251
|
</div>
|
|
246
252
|
</div>
|
|
247
253
|
</td>
|
|
248
|
-
<td
|
|
254
|
+
<td
|
|
255
|
+
className="hidden max-w-xs truncate whitespace-nowrap px-3 py-4 text-sm text-gray-500 md:table-cell md:px-6"
|
|
256
|
+
title={belief.slug}
|
|
257
|
+
>
|
|
249
258
|
{belief.slug}
|
|
250
259
|
</td>
|
|
251
260
|
<td className="hidden whitespace-nowrap px-3 py-4 text-sm md:table-cell md:px-6">
|
|
@@ -142,7 +142,7 @@ const KnownResourceTable = ({
|
|
|
142
142
|
</div>
|
|
143
143
|
</div>
|
|
144
144
|
|
|
145
|
-
<div className="overflow-
|
|
145
|
+
<div className="overflow-x-auto shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
|
|
146
146
|
<table className="min-w-full divide-y divide-gray-300">
|
|
147
147
|
<thead className="bg-gray-50">
|
|
148
148
|
<tr>
|
|
@@ -185,7 +185,10 @@ const KnownResourceTable = ({
|
|
|
185
185
|
className="cursor-pointer hover:bg-gray-50"
|
|
186
186
|
onClick={() => onEdit(categorySlug)}
|
|
187
187
|
>
|
|
188
|
-
<td
|
|
188
|
+
<td
|
|
189
|
+
className="max-w-xs truncate whitespace-nowrap px-6 py-4 text-sm font-bold text-gray-900"
|
|
190
|
+
title={categorySlug}
|
|
191
|
+
>
|
|
189
192
|
{categorySlug}
|
|
190
193
|
</td>
|
|
191
194
|
<td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
|