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