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.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/bin/create-tractstack.js +2 -2
  3. package/dist/index.js +94 -16
  4. package/package.json +2 -2
  5. package/templates/custom/minimal/CodeHook.astro +10 -2
  6. package/templates/custom/shopify/Cart.tsx +100 -73
  7. package/templates/custom/shopify/CheckoutModal.tsx +509 -120
  8. package/templates/custom/shopify/NativeBookingCalendar.tsx +375 -0
  9. package/templates/custom/shopify/ShopifyCartManager.tsx +92 -37
  10. package/templates/custom/shopify/ShopifyProductGrid.tsx +139 -173
  11. package/templates/custom/shopify/ShopifyServiceList.tsx +20 -3
  12. package/templates/custom/with-examples/CodeHook.astro +10 -2
  13. package/templates/src/components/Footer.astro +4 -4
  14. package/templates/src/components/Header.astro +9 -3
  15. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +3 -3
  16. package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +2 -2
  17. package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +2 -2
  18. package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +3 -3
  19. package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +2 -2
  20. package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +7 -7
  21. package/templates/src/components/form/advanced/APIConfigSection.tsx +244 -2
  22. package/templates/src/components/form/shopify/SchedulingSection.tsx +354 -0
  23. package/templates/src/components/storykeep/Dashboard.tsx +1 -1
  24. package/templates/src/components/storykeep/Dashboard_Shopify.tsx +253 -110
  25. package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +14 -5
  26. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +5 -2
  27. package/templates/src/components/storykeep/controls/content/MenuTable.tsx +14 -5
  28. package/templates/src/components/storykeep/controls/content/ProductTable.tsx +180 -101
  29. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +9 -5
  30. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +13 -4
  31. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +14 -5
  32. package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +111 -0
  33. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +393 -0
  34. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Products.tsx +46 -0
  35. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Schedule.tsx +78 -0
  36. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx +55 -0
  37. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Services.tsx +47 -0
  38. package/templates/src/pages/api/auth/lookup-lead.ts +72 -0
  39. package/templates/src/pages/api/booking/availability.ts +72 -0
  40. package/templates/src/pages/api/booking/cancel.ts +73 -0
  41. package/templates/src/pages/api/booking/confirm.ts +82 -0
  42. package/templates/src/pages/api/booking/hold.ts +75 -0
  43. package/templates/src/pages/api/booking/list.ts +66 -0
  44. package/templates/src/pages/api/booking/metrics.ts +60 -0
  45. package/templates/src/pages/api/booking/release.ts +76 -0
  46. package/templates/src/pages/api/sandbox.ts +2 -2
  47. package/templates/src/pages/api/shopify/createCart.ts +4 -8
  48. package/templates/src/pages/api/shopify/getProducts.ts +15 -15
  49. package/templates/src/pages/storykeep/login.astro +21 -14
  50. package/templates/src/stores/shopify.ts +81 -25
  51. package/templates/src/types/tractstack.ts +54 -0
  52. package/templates/src/utils/api/advancedConfig.ts +2 -0
  53. package/templates/src/utils/api/advancedHelpers.ts +40 -3
  54. package/templates/src/utils/api/bookingHelpers.ts +125 -0
  55. package/templates/src/utils/api/brandHelpers.ts +10 -0
  56. package/templates/src/utils/auth.ts +29 -9
  57. package/templates/src/utils/compositor/aiGeneration.ts +3 -3
  58. package/templates/src/utils/compositor/aiPaneParser.ts +2 -2
  59. package/templates/src/utils/customHelpers.ts +0 -21
  60. package/templates/src/utils/profileStorage.ts +5 -0
  61. package/templates/src/utils/tenantResolver.ts +2 -1
  62. package/utils/inject-files.ts +82 -4
  63. 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
- fetchShopifyProducts,
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
- startCreateFlow('service', product);
141
+ executePreFlightCheck('service', product);
133
142
  } else {
134
- startCreateFlow('product', product);
143
+ executePreFlightCheck('product', product);
135
144
  }
136
145
  };
137
146
 
138
- const handleEdit = (_product: ShopifyProduct, resource: ResourceNode) => {
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
- if (machineState === 'INIT') {
273
- return null;
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
- {status.error && (
350
- <div className="mb-6 rounded-md bg-red-50 p-4">
351
- <div className="flex">
352
- <div className="flex-shrink-0">
353
- <span className="text-red-400">⚠️</span>
354
- </div>
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
- </div>
363
- )}
364
-
365
- {status.isLoading && data.products.length === 0 ? (
366
- <div className="flex h-64 items-center justify-center">
367
- <div className="text-center">
368
- <div className="mx-auto h-8 w-8 animate-spin rounded-full border-2 border-gray-300 border-t-cyan-600"></div>
369
- <p className="mt-2 text-sm text-gray-500">
370
- Loading products from Shopify...
371
- </p>
372
- </div>
373
- </div>
374
- ) : (
375
- <ProductTable
376
- products={data.products}
377
- linkedResourceMap={linkedResourceMap}
378
- onRefresh={handleRefresh}
379
- isRefreshing={status.isLoading}
380
- onSelectProduct={setSelectedProduct}
381
- onLink={handleLink}
382
- onUnlink={handleUnlink}
383
- onEdit={handleEdit}
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
- {/* Product Inspector Modal */}
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-500"
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-4">
410
- <h4 className="mb-2 text-sm font-bold text-gray-900">
411
- Product Details
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
- <dl className="grid grid-cols-1 gap-x-4 gap-y-4 md:grid-cols-2">
414
- <div className="md:col-span-1">
415
- <dt className="text-sm font-bold text-gray-500">
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
- {JSON.stringify(selectedProduct, null, 2)}
446
- </pre>
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 imported as a Product or
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={() => startCreateFlow('product', targetProduct)}
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={() => startCreateFlow('service', targetProduct)}
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
- <div className="mt-4 border-t pt-4">
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={() => setShowTypeSelector(false)}
485
- className="w-full text-center text-sm text-gray-500 hover:text-gray-700"
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} // Use local resources for slug uniqueness check
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-hidden rounded-lg border border-gray-200 bg-white shadow">
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="overflow-hidden">
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 className="text-sm font-bold text-gray-900">
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 className="text-sm text-gray-500 md:hidden">
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 className="hidden whitespace-nowrap px-3 py-4 text-sm text-gray-500 md:table-cell md:px-6">
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-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
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 className="whitespace-nowrap px-6 py-4 text-sm font-bold text-gray-900">
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-hidden rounded-lg border border-gray-200 bg-white shadow">
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="overflow-hidden">
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 className="text-sm font-bold text-gray-900">
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 className="text-sm text-gray-500 md:hidden">
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 className="hidden whitespace-nowrap px-3 py-4 text-sm text-gray-500 md:table-cell md:px-6">
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">