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.
Files changed (95) hide show
  1. package/README.md +1 -1
  2. package/bin/create-tractstack.js +2 -2
  3. package/dist/index.js +130 -19
  4. package/package.json +2 -2
  5. package/templates/custom/minimal/CodeHook.astro +10 -2
  6. package/templates/custom/shopify/Cart.tsx +115 -77
  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 +91 -45
  10. package/templates/custom/shopify/ShopifyCheckout.tsx +4 -33
  11. package/templates/custom/shopify/ShopifyProductGrid.tsx +170 -176
  12. package/templates/custom/shopify/ShopifyServiceList.tsx +112 -51
  13. package/templates/custom/with-examples/CodeHook.astro +10 -2
  14. package/templates/src/components/Footer.astro +6 -6
  15. package/templates/src/components/Header.astro +23 -11
  16. package/templates/src/components/Menu.tsx +157 -135
  17. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
  18. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +27 -6
  19. package/templates/src/components/codehooks/EpinetTableView.tsx +153 -112
  20. package/templates/src/components/codehooks/EpinetWrapper.tsx +4 -1
  21. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +8 -1
  22. package/templates/src/components/codehooks/ProductCardSetup.tsx +9 -1
  23. package/templates/src/components/codehooks/ProductGridSetup.tsx +9 -1
  24. package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +2 -1
  25. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +1 -1
  26. package/templates/src/components/edit/ToolBar.tsx +2 -1
  27. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +2 -2
  28. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +13 -0
  29. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +3 -3
  30. package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +2 -2
  31. package/templates/src/components/edit/pane/AiRestylePaneModal.tsx +2 -2
  32. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  33. package/templates/src/components/edit/pane/steps/AiCreativeDesignStep.tsx +2 -2
  34. package/templates/src/components/edit/pane/steps/AiLibraryCopyStep.tsx +3 -3
  35. package/templates/src/components/edit/pane/steps/AiRefineDesignStep.tsx +2 -2
  36. package/templates/src/components/edit/pane/steps/AiStandardDesignStep.tsx +7 -7
  37. package/templates/src/components/edit/state/SaveModal.tsx +1 -1
  38. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +8 -3
  39. package/templates/src/components/form/DateTimeInput.tsx +10 -3
  40. package/templates/src/components/form/FileUpload.tsx +11 -5
  41. package/templates/src/components/form/NumberInput.tsx +2 -2
  42. package/templates/src/components/form/advanced/APIConfigSection.tsx +208 -2
  43. package/templates/src/components/form/brand/SiteConfigSection.tsx +10 -0
  44. package/templates/src/components/form/shopify/SchedulingSection.tsx +354 -0
  45. package/templates/src/components/storykeep/Dashboard.tsx +1 -1
  46. package/templates/src/components/storykeep/Dashboard_Shopify.tsx +252 -110
  47. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +2 -2
  48. package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +14 -5
  49. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +5 -2
  50. package/templates/src/components/storykeep/controls/content/MenuTable.tsx +14 -5
  51. package/templates/src/components/storykeep/controls/content/ProductTable.tsx +180 -101
  52. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +88 -56
  53. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +14 -4
  54. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +14 -5
  55. package/templates/src/components/storykeep/email-builder/Blocks.tsx +169 -0
  56. package/templates/src/components/storykeep/email-builder/EmailBuilder.tsx +223 -0
  57. package/templates/src/components/storykeep/email-builder/PreviewModal.tsx +136 -0
  58. package/templates/src/components/storykeep/email-builder/PropertyPanel.tsx +154 -0
  59. package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +104 -0
  60. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +419 -0
  61. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx +105 -0
  62. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Products.tsx +46 -0
  63. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Schedule.tsx +78 -0
  64. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Search.tsx +55 -0
  65. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Services.tsx +47 -0
  66. package/templates/src/layouts/Layout.astro +8 -5
  67. package/templates/src/pages/api/auth/lookup-lead.ts +72 -0
  68. package/templates/src/pages/api/booking/availability.ts +72 -0
  69. package/templates/src/pages/api/booking/cancel.ts +73 -0
  70. package/templates/src/pages/api/booking/confirm.ts +82 -0
  71. package/templates/src/pages/api/booking/hold.ts +75 -0
  72. package/templates/src/pages/api/booking/list.ts +66 -0
  73. package/templates/src/pages/api/booking/metrics.ts +60 -0
  74. package/templates/src/pages/api/booking/release.ts +76 -0
  75. package/templates/src/pages/api/sandbox.ts +2 -2
  76. package/templates/src/pages/api/shopify/createCart.ts +4 -8
  77. package/templates/src/pages/api/shopify/getProducts.ts +15 -15
  78. package/templates/src/pages/storykeep/login.astro +21 -14
  79. package/templates/src/stores/shopify.ts +97 -25
  80. package/templates/src/types/formTypes.ts +4 -2
  81. package/templates/src/types/tractstack.ts +59 -2
  82. package/templates/src/utils/api/advancedConfig.ts +2 -0
  83. package/templates/src/utils/api/advancedHelpers.ts +40 -3
  84. package/templates/src/utils/api/bookingHelpers.ts +125 -0
  85. package/templates/src/utils/api/brandConfig.ts +2 -0
  86. package/templates/src/utils/api/brandHelpers.ts +26 -0
  87. package/templates/src/utils/api/emailHelpers.ts +105 -0
  88. package/templates/src/utils/auth.ts +29 -9
  89. package/templates/src/utils/compositor/aiGeneration.ts +3 -3
  90. package/templates/src/utils/compositor/aiPaneParser.ts +2 -2
  91. package/templates/src/utils/customHelpers.ts +0 -21
  92. package/templates/src/utils/profileStorage.ts +5 -0
  93. package/templates/src/utils/tenantResolver.ts +3 -2
  94. package/utils/inject-files.ts +116 -5
  95. package/templates/custom/shopify/CalDotComBooking.tsx +0 -44
@@ -1,5 +1,6 @@
1
1
  import { useState, useEffect } from 'react';
2
2
  import { useStore } from '@nanostores/react';
3
+ import { classNames } from '@/utils/helpers';
3
4
  import { epinetCustomFilters } from '@/stores/analytics';
4
5
  import { Accordion } from '@ark-ui/react';
5
6
  import ChevronLeftIcon from '@heroicons/react/24/outline/ChevronLeftIcon';
@@ -68,6 +69,7 @@ const EpinetTableView = ({
68
69
  const [currentDay, setCurrentDay] = useState<string | null>(null);
69
70
  const [availableDays, setAvailableDays] = useState<string[]>([]);
70
71
  const [currentDayIndex, setCurrentDayIndex] = useState(0);
72
+ const [openAccordionValues, setOpenAccordionValues] = useState<string[]>([]);
71
73
 
72
74
  const getContentInfo = (
73
75
  contentId: string
@@ -215,6 +217,12 @@ const EpinetTableView = ({
215
217
  setCurrentDay(availableDays[newIndex]);
216
218
  };
217
219
 
220
+ useEffect(() => {
221
+ setOpenAccordionValues((prev) =>
222
+ prev.filter((v) => !v.startsWith('empty-'))
223
+ );
224
+ }, [currentDay]);
225
+
218
226
  const getCurrentDayData = (): {
219
227
  data: HourData[];
220
228
  dailyTotal: number;
@@ -485,129 +493,162 @@ const EpinetTableView = ({
485
493
  </div>
486
494
  </div>
487
495
 
488
- <Accordion.Root multiple className="w-full">
489
- {dayData.map((item, index) => (
490
- <Accordion.Item
491
- key={item.type === 'active' ? item.hourKey : `empty-${index}`}
492
- value={item.type === 'active' ? item.hourKey : `empty-${index}`}
493
- className="border-b border-gray-100 last:border-b-0"
494
- >
495
- <Accordion.ItemTrigger className="flex w-full cursor-pointer items-center justify-between p-3 text-left transition-colors duration-200 hover:bg-gray-100">
496
- {item.type === 'active' ? (
497
- <div className="flex flex-grow items-center justify-between space-x-3">
498
- <div className="flex flex-grow items-center space-x-3">
499
- <span className="text-sm font-bold text-gray-700">
500
- {item.humanReadableTime}
501
- </span>
502
- <span className="text-xs text-gray-600">
503
- {item.hourlyTotal} event
504
- {item.hourlyTotal !== 1 ? 's' : ''} /{' '}
505
- {item.hourlyVisitors} visitor
506
- {item.hourlyVisitors !== 1 ? 's' : ''}
507
- </span>
508
- <div className="relative h-2 w-full max-w-48 rounded bg-gray-200">
509
- <div
510
- className="absolute left-0 top-0 h-2 rounded bg-cyan-600"
511
- style={{
512
- width: `${Math.max(item.relativeToMax * 100, 5)}%`,
513
- }}
514
- title={`${item.hourlyTotal} events (${(
515
- item.relativeToMax * 100
516
- ).toFixed(1)}% of busiest hour)`}
517
- />
496
+ <Accordion.Root
497
+ multiple
498
+ className="w-full"
499
+ value={openAccordionValues}
500
+ onValueChange={({ value }) =>
501
+ setOpenAccordionValues(value.filter((v) => !v.startsWith('empty-')))
502
+ }
503
+ >
504
+ {dayData.map((item, index) => {
505
+ const itemValue =
506
+ item.type === 'active' ? item.hourKey : `empty-${index}`;
507
+ const isExpandable = item.type === 'active';
508
+ const isOpen = openAccordionValues.includes(itemValue);
509
+ return (
510
+ <Accordion.Item
511
+ key={item.type === 'active' ? item.hourKey : `empty-${index}`}
512
+ value={itemValue}
513
+ disabled={!isExpandable}
514
+ className="border-b border-gray-100 last:border-b-0"
515
+ >
516
+ <Accordion.ItemTrigger
517
+ className={classNames(
518
+ 'flex w-full items-center justify-between p-3 text-left transition-colors duration-200',
519
+ isExpandable
520
+ ? 'cursor-pointer hover:bg-gray-100'
521
+ : 'cursor-default hover:bg-transparent'
522
+ )}
523
+ >
524
+ {item.type === 'active' ? (
525
+ <div className="flex flex-grow items-center justify-between space-x-3">
526
+ <div className="flex flex-grow items-center space-x-3">
527
+ <span className="text-sm font-bold text-gray-700">
528
+ {item.humanReadableTime}
529
+ </span>
530
+ <span className="text-xs text-gray-600">
531
+ {item.hourlyTotal} event
532
+ {item.hourlyTotal !== 1 ? 's' : ''} /{' '}
533
+ {item.hourlyVisitors} visitor
534
+ {item.hourlyVisitors !== 1 ? 's' : ''}
535
+ </span>
536
+ <div className="relative h-2 w-full max-w-48 rounded bg-gray-200">
537
+ <div
538
+ className="absolute left-0 top-0 h-2 rounded bg-cyan-600"
539
+ style={{
540
+ width: `${Math.max(item.relativeToMax * 100, 5)}%`,
541
+ }}
542
+ title={`${item.hourlyTotal} events (${(
543
+ item.relativeToMax * 100
544
+ ).toFixed(1)}% of busiest hour)`}
545
+ />
546
+ </div>
518
547
  </div>
519
- </div>
520
- <div className="flex items-center space-x-2">
521
- <div
522
- onClick={(e) => {
523
- e.stopPropagation();
524
- focusOnThisHour(item.hourKey);
525
- }}
526
- className="flex cursor-pointer items-center rounded-md bg-orange-100 px-2 py-1 text-xs font-bold text-orange-800 transition-colors duration-200 hover:bg-orange-200"
527
- role="button"
528
- tabIndex={0}
529
- onKeyDown={(e) => {
530
- if (e.key === 'Enter' || e.key === ' ') {
531
- e.preventDefault();
548
+ <div className="flex items-center space-x-2">
549
+ <div
550
+ onClick={(e) => {
532
551
  e.stopPropagation();
533
552
  focusOnThisHour(item.hourKey);
534
- }
535
- }}
536
- >
537
- <MagnifyingGlassIcon className="mr-1 h-3 w-3" />
538
- Journeys this Hour
553
+ }}
554
+ className="flex cursor-pointer items-center rounded-md bg-orange-100 px-2 py-1 text-xs font-bold text-orange-800 transition-colors duration-200 hover:bg-orange-200"
555
+ role="button"
556
+ tabIndex={0}
557
+ onKeyDown={(e) => {
558
+ if (e.key === 'Enter' || e.key === ' ') {
559
+ e.preventDefault();
560
+ e.stopPropagation();
561
+ focusOnThisHour(item.hourKey);
562
+ }
563
+ }}
564
+ >
565
+ <MagnifyingGlassIcon className="mr-1 h-3 w-3" />
566
+ Journeys this Hour
567
+ </div>
568
+ <div className="flex items-center rounded-md bg-orange-100 px-2 py-1 text-xs font-bold text-orange-800">
569
+ <Accordion.ItemIndicator>
570
+ <ChevronDownIcon
571
+ className={classNames(
572
+ 'h-3 w-3 transition-transform duration-200',
573
+ isOpen && 'rotate-180'
574
+ )}
575
+ />
576
+ </Accordion.ItemIndicator>
577
+ <span
578
+ className={classNames(
579
+ 'ml-1',
580
+ isOpen ? 'hidden' : 'block'
581
+ )}
582
+ >
583
+ Expand Details
584
+ </span>
585
+ <span
586
+ className={classNames(
587
+ 'ml-1',
588
+ isOpen ? 'block' : 'hidden'
589
+ )}
590
+ >
591
+ Hide Details
592
+ </span>
593
+ </div>
539
594
  </div>
540
- <div className="flex items-center rounded-md bg-orange-100 px-2 py-1 text-xs font-bold text-orange-800">
541
- <Accordion.ItemIndicator>
542
- <ChevronDownIcon className="h-3 w-3 transition-transform duration-200 data-[state=open]:rotate-180" />
543
- </Accordion.ItemIndicator>
544
- <span className="ml-1 data-[state=closed]:block data-[state=open]:hidden">
545
- Expand Details
595
+ </div>
596
+ ) : (
597
+ <div className="flex flex-grow items-center">
598
+ <div className="flex items-center">
599
+ <span className="text-sm text-gray-700">
600
+ {item.humanReadableDisplay}
546
601
  </span>
547
- <span className="ml-1 data-[state=open]:block data-[state=closed]:hidden">
548
- Hide Details
602
+ <span className="ml-2 text-xs italic text-gray-500">
603
+ {item.isFuture ? 'The future awaits!' : 'No activity'}
549
604
  </span>
550
605
  </div>
551
606
  </div>
552
- </div>
553
- ) : (
554
- <div className="flex flex-grow items-center justify-between">
555
- <div className="flex items-center">
556
- <span className="text-sm text-gray-700">
557
- {item.humanReadableDisplay}
558
- </span>
559
- <span className="ml-2 text-xs italic text-gray-500">
560
- {item.isFuture ? 'The future awaits!' : 'No activity'}
561
- </span>
562
- </div>
563
- <Accordion.ItemIndicator>
564
- <ChevronDownIcon className="h-5 w-5 text-gray-500 transition-transform duration-200 data-[state=open]:rotate-180" />
565
- </Accordion.ItemIndicator>
566
- </div>
567
- )}
568
- </Accordion.ItemTrigger>
569
-
570
- <Accordion.ItemContent className="p-4">
571
- {item.type === 'active' && (
572
- <div className="space-y-4">
573
- {item.contentItems.map((content) => (
574
- <div
575
- key={`${item.hourKey}-${content.contentId}`}
576
- className="mb-3"
577
- >
578
- <div className="mb-1 flex items-center justify-between text-sm font-bold text-gray-700">
579
- <div className="flex items-center">
580
- {getContentIcon(content.contentType)}
581
- {content.title}
582
- </div>
583
- {content.visitorIds.length > 0 && (
584
- <div
585
- className="flex items-center text-xs text-gray-600"
586
- title={content.visitorIds.join(', ')}
587
- >
588
- <UserGroupIcon className="mr-1 h-3 w-3" />
589
- {content.visitorIds.length} unique visitor
590
- {content.visitorIds.length !== 1 ? 's' : ''}
591
- </div>
592
- )}
593
- </div>
594
- <div className="flex flex-wrap gap-2">
595
- {content.events.map((event, eventIdx) => (
596
- <div
597
- key={`${item.hourKey}-${content.contentId}-${event.verb}-${eventIdx}`}
598
- className="rounded-full bg-cyan-100 px-2 py-0.5 text-xs font-bold text-cyan-800"
599
- >
600
- {event.verb} [{event.count}]
607
+ )}
608
+ </Accordion.ItemTrigger>
609
+
610
+ {isExpandable && (
611
+ <Accordion.ItemContent className="p-4">
612
+ <div className="space-y-4">
613
+ {item.contentItems.map((content) => (
614
+ <div
615
+ key={`${item.hourKey}-${content.contentId}`}
616
+ className="mb-3"
617
+ >
618
+ <div className="mb-1 flex items-center justify-between text-sm font-bold text-gray-700">
619
+ <div className="flex items-center">
620
+ {getContentIcon(content.contentType)}
621
+ {content.title}
601
622
  </div>
602
- ))}
623
+ {content.visitorIds.length > 0 && (
624
+ <div
625
+ className="flex items-center text-xs text-gray-600"
626
+ title={content.visitorIds.join(', ')}
627
+ >
628
+ <UserGroupIcon className="mr-1 h-3 w-3" />
629
+ {content.visitorIds.length} unique visitor
630
+ {content.visitorIds.length !== 1 ? 's' : ''}
631
+ </div>
632
+ )}
633
+ </div>
634
+ <div className="flex flex-wrap gap-2">
635
+ {content.events.map((event, eventIdx) => (
636
+ <div
637
+ key={`${item.hourKey}-${content.contentId}-${event.verb}-${eventIdx}`}
638
+ className="rounded-full bg-cyan-100 px-2 py-0.5 text-xs font-bold text-cyan-800"
639
+ >
640
+ {event.verb} [{event.count}]
641
+ </div>
642
+ ))}
643
+ </div>
603
644
  </div>
604
- </div>
605
- ))}
606
- </div>
645
+ ))}
646
+ </div>
647
+ </Accordion.ItemContent>
607
648
  )}
608
- </Accordion.ItemContent>
609
- </Accordion.Item>
610
- ))}
649
+ </Accordion.Item>
650
+ );
651
+ })}
611
652
 
612
653
  {dayData.length === 0 && (
613
654
  <div className="py-6 text-center text-sm text-gray-500">
@@ -269,7 +269,10 @@ const EpinetWrapper = ({
269
269
  return (
270
270
  <div className="flex h-96 w-full items-center justify-center rounded bg-gray-100">
271
271
  <div className="text-center">
272
- <div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-cyan-600 border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]"></div>
272
+ <div
273
+ className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-cyan-600 border-r-transparent motion-reduce:animate-none"
274
+ style={{ verticalAlign: '-0.125em' }}
275
+ ></div>
273
276
  <p className="mt-4 text-sm text-gray-600">
274
277
  Discovering analytics configuration...
275
278
  </p>
@@ -22,6 +22,13 @@ const comboboxItemStyles = `
22
22
  .combo-item[data-state="checked"] .check-indicator {
23
23
  display: flex;
24
24
  }
25
+ .combo-item[data-highlighted] {
26
+ background-color: #0891b2;
27
+ color: #fff;
28
+ }
29
+ .combo-item[data-highlighted] .check-indicator {
30
+ color: #fff;
31
+ }
25
32
  `;
26
33
 
27
34
  const FeaturedArticleSetup = ({
@@ -268,7 +275,7 @@ const FeaturedArticleSetup = ({
268
275
  <Combobox.Item
269
276
  key={item.slug}
270
277
  item={item}
271
- className="combo-item relative cursor-default select-none py-2 pl-10 pr-4 text-gray-900 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
278
+ className="combo-item relative cursor-default select-none py-2 pl-10 pr-4 text-gray-900"
272
279
  >
273
280
  <span className="block truncate">{item.title}</span>
274
281
  <span className="check-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
@@ -7,6 +7,13 @@ import { fullContentMapStore } from '@/stores/storykeep';
7
7
  import { getCtx } from '@/stores/nodes';
8
8
  import type { PaneNode } from '@/types/compositorTypes';
9
9
 
10
+ const productCardComboItemHighlightStyles = `
11
+ .product-card-combo-item[data-highlighted] {
12
+ background-color: #0891b2;
13
+ color: #fff;
14
+ }
15
+ `;
16
+
10
17
  interface ProductCardSetupProps {
11
18
  nodeId: string;
12
19
  params: Record<string, any> | null;
@@ -118,6 +125,7 @@ export const ProductCardSetup = (props: ProductCardSetupProps) => {
118
125
  <Combobox.Label className="text-sm font-bold text-gray-700">
119
126
  Find a product
120
127
  </Combobox.Label>
128
+ <style>{productCardComboItemHighlightStyles}</style>
121
129
  <Combobox.Control>
122
130
  <Combobox.Input
123
131
  className="w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 md:text-sm"
@@ -131,7 +139,7 @@ export const ProductCardSetup = (props: ProductCardSetupProps) => {
131
139
  <Combobox.Item
132
140
  key={item.value}
133
141
  item={item}
134
- className="relative cursor-pointer select-none px-4 py-2 text-gray-900 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
142
+ className="product-card-combo-item relative cursor-pointer select-none px-4 py-2 text-gray-900"
135
143
  >
136
144
  <Combobox.ItemText>{item.label}</Combobox.ItemText>
137
145
  </Combobox.Item>
@@ -35,6 +35,13 @@ const modes = [
35
35
  },
36
36
  ];
37
37
 
38
+ const productGridComboItemHighlightStyles = `
39
+ .product-grid-combo-item[data-highlighted] {
40
+ background-color: #0891b2;
41
+ color: #fff;
42
+ }
43
+ `;
44
+
38
45
  export const ProductGridSetup = (props: ProductGridSetupProps) => {
39
46
  const { nodeId, params } = props;
40
47
  const ctx = getCtx();
@@ -235,6 +242,7 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
235
242
  <Combobox.Label className="text-sm font-bold text-gray-700">
236
243
  Find products to include
237
244
  </Combobox.Label>
245
+ <style>{productGridComboItemHighlightStyles}</style>
238
246
  <Combobox.Control>
239
247
  <Combobox.Input
240
248
  className="w-full rounded-md border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-500 focus:ring-cyan-500 md:text-sm"
@@ -248,7 +256,7 @@ export const ProductGridSetup = (props: ProductGridSetupProps) => {
248
256
  <Combobox.Item
249
257
  key={item.value}
250
258
  item={item}
251
- className="relative flex cursor-pointer select-none items-center px-4 py-2 text-gray-900 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
259
+ className="product-grid-combo-item relative flex cursor-pointer select-none items-center px-4 py-2 text-gray-900"
252
260
  >
253
261
  <Combobox.ItemText>{item.label}</Combobox.ItemText>
254
262
  <Combobox.ItemIndicator className="ml-auto">
@@ -1,3 +1,4 @@
1
+ import type { MouseEvent } from 'react';
1
2
  import { getCtx } from '@/stores/nodes';
2
3
  import { viewportKeyStore } from '@/stores/storykeep';
3
4
  import {
@@ -15,7 +16,7 @@ export const BgPaneWrapper = (props: NodeProps) => {
15
16
 
16
17
  const viewport = viewportKeyStore.get().value;
17
18
 
18
- const handleClick = (e: React.MouseEvent) => {
19
+ const handleClick = (e: MouseEvent) => {
19
20
  getCtx(props).setClickedNodeId(props.nodeId, true);
20
21
  e.stopPropagation();
21
22
  };
@@ -85,7 +85,7 @@ export const GhostInsertBlock = memo((props: GhostInsertBlockProps) => {
85
85
  setShowInsertOptions(false);
86
86
  };
87
87
 
88
- const handleClose = (e: React.MouseEvent) => {
88
+ const handleClose = (e: MouseEvent) => {
89
89
  e.stopPropagation();
90
90
  getCtx(props).setPanelMode('', '', '');
91
91
  setShowInsertOptions(false);
@@ -1,3 +1,4 @@
1
+ import type { MouseEvent } from 'react';
1
2
  import { useStore } from '@nanostores/react';
2
3
  import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
3
4
  import { getCtx } from '@/stores/nodes';
@@ -20,7 +21,7 @@ const AddElementsPanel = ({
20
21
  ctx.notifyNode('root');
21
22
  };
22
23
 
23
- const handleMouseDown = (e: React.MouseEvent, mode: ToolAddMode) => {
24
+ const handleMouseDown = (e: MouseEvent, mode: ToolAddMode) => {
24
25
  e.preventDefault();
25
26
  startToolDrag('insert', mode, e.clientX, e.clientY);
26
27
  initToolDragListeners();
@@ -1,4 +1,4 @@
1
- import { useState, useEffect } from 'react';
1
+ import { useState, useEffect, type ChangeEvent } from 'react';
2
2
  import ExclamationTriangleIcon from '@heroicons/react/24/outline/ExclamationTriangleIcon';
3
3
  import CheckIcon from '@heroicons/react/24/outline/CheckIcon';
4
4
  import { getCtx } from '@/stores/nodes';
@@ -42,7 +42,7 @@ const PaneSlugPanel = ({ nodeId, setMode }: PaneSlugPanelProps) => {
42
42
  //.replace(/^-+|-+$/g, '');
43
43
  };
44
44
 
45
- const handleSlugChange = (e: React.ChangeEvent<HTMLInputElement>) => {
45
+ const handleSlugChange = (e: ChangeEvent<HTMLInputElement>) => {
46
46
  const newSlug = validateSlug(e.target.value);
47
47
  if (newSlug.length <= 75) {
48
48
  // Prevent more than 75 chars
@@ -103,6 +103,19 @@ const AddPaneCodeHookPanel = ({
103
103
  parentId: '',
104
104
  codeHookTarget: selected,
105
105
  isContextPane: isContextPane,
106
+ ...(selected === 'shopify-product-grid' ||
107
+ selected === 'shopify-service-list'
108
+ ? {
109
+ codeHookPayload: {
110
+ options: JSON.stringify({
111
+ category: 'product|service',
112
+ group: '',
113
+ title: '',
114
+ bgColor: '#f9f9f9',
115
+ }),
116
+ },
117
+ }
118
+ : {}),
106
119
  };
107
120
  const targetId =
108
121
  isStoryFragment || isContextPane
@@ -276,9 +276,9 @@ const AddPaneNewPanel = ({
276
276
  {!hasAssemblyAI && (
277
277
  <div className="rounded-lg border-l-4 border-blue-400 bg-blue-50 p-4 shadow-sm">
278
278
  <p className="text-sm text-blue-800">
279
- Tract Stack uses AssemblyAI AskLemur service to generate designs,
280
- describe content, and streamline the management of your site. We
281
- strongly recommend enabling these features. See{' '}
279
+ Tract Stack uses AssemblyAI to generate designs, describe content,
280
+ and streamline the management of your site. We strongly recommend
281
+ enabling these features. See{' '}
282
282
  <a
283
283
  href="https://freewebpress.org"
284
284
  target="_blank"
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useState, type ChangeEvent } from 'react';
2
2
 
3
3
  interface AddPaneNewCustomCopyProps {
4
4
  value: string;
@@ -11,7 +11,7 @@ export const AddPaneNewCustomCopy = ({
11
11
  }: AddPaneNewCustomCopyProps) => {
12
12
  const [localValue, setLocalValue] = useState(initialValue);
13
13
 
14
- const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
14
+ const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
15
15
  setLocalValue(e.target.value);
16
16
  };
17
17
 
@@ -11,7 +11,7 @@ import { AiDesignStep, type AiDesignConfig } from './steps/AiDesignStep';
11
11
  import { AiRefineDesignStep } from './steps/AiRefineDesignStep';
12
12
  import prompts from '@/constants/prompts.json';
13
13
  import { parseAiPane } from '@/utils/compositor/aiPaneParser';
14
- import { callAskLemurAPI } from '@/utils/compositor/aiGeneration';
14
+ import { callAaiAPI } from '@/utils/compositor/aiGeneration';
15
15
  import type { PaneNode, TemplatePane } from '@/types/compositorTypes';
16
16
 
17
17
  interface AiRestylePaneModalProps {
@@ -87,7 +87,7 @@ export const AiRestylePaneModal = ({
87
87
  .replace('{{COPY_INPUT}}', 'A generic content section')
88
88
  .replace('{{LAYOUT_TYPE}}', 'Text Only');
89
89
 
90
- const resultStr = await callAskLemurAPI({
90
+ const resultStr = await callAaiAPI({
91
91
  prompt: formattedPrompt,
92
92
  context: shellPromptDetails.system || '',
93
93
  expectJson: true,
@@ -302,7 +302,7 @@ const ConfigPanePanel = ({
302
302
  {/* Right Aligned Tools */}
303
303
  <div className="ml-auto flex items-center gap-2 px-2">
304
304
  {/* Delete & Reorder Tools */}
305
- {!isTemplate && !isContextPane && !isHtmlAstPane && (
305
+ {!isTemplate && !isContextPane && (
306
306
  <div className="flex items-center gap-1 border-r border-gray-300 pr-2">
307
307
  <button
308
308
  onClick={handleMoveUp}
@@ -6,7 +6,7 @@ import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRou
6
6
  import prompts from '@/constants/prompts.json';
7
7
  import { htmlToHtmlAst } from '@/utils/compositor/htmlAst';
8
8
  import type { TemplatePane } from '@/types/compositorTypes';
9
- import { callAskLemurAPI } from '@/utils/compositor/aiGeneration';
9
+ import { callAaiAPI } from '@/utils/compositor/aiGeneration';
10
10
  import BooleanToggle from '@/components/form/BooleanToggle';
11
11
  import { AiDesignStep, type AiDesignConfig } from './AiDesignStep';
12
12
 
@@ -86,7 +86,7 @@ export const AiCreativeDesignStep = ({
86
86
  userPrompt = userPrompt.replace('{{DESIGN_NOTES}}', combinedNotes);
87
87
 
88
88
  // Use shared infrastructure utility
89
- const rawHtml = await callAskLemurAPI({
89
+ const rawHtml = await callAaiAPI({
90
90
  prompt: userPrompt,
91
91
  context: systemPrompt,
92
92
  expectJson: false,
@@ -6,7 +6,7 @@ import {
6
6
  convertTemplateToAIShell,
7
7
  } from '@/utils/compositor/designLibraryHelper';
8
8
  import { parseAiPane, parseAiCopyHtml } from '@/utils/compositor/aiPaneParser';
9
- import { callAskLemurAPI } from '@/utils/compositor/aiGeneration';
9
+ import { callAaiAPI } from '@/utils/compositor/aiGeneration';
10
10
  import { CopyInputStep, type CopyMode } from './CopyInputStep';
11
11
  import type { DesignLibraryEntry } from '@/types/tractstack';
12
12
  import type { TemplatePane } from '@/types/compositorTypes';
@@ -159,7 +159,7 @@ export const AiLibraryCopyStep = ({
159
159
  .replace('{{LAYOUT_TYPE}}', layoutType)
160
160
  .replace('{{COLUMN_EXAMPLE}}', columnPreset.example);
161
161
 
162
- const copyResult = await callAskLemurAPI({
162
+ const copyResult = await callAaiAPI({
163
163
  prompt: formattedCopyPrompt,
164
164
  context: copyPromptDetails.system || '',
165
165
  expectJson: false,
@@ -201,7 +201,7 @@ export const AiLibraryCopyStep = ({
201
201
  .replace('{{LAYOUT_TYPE}}', layoutType)
202
202
  .replace('{{SHELL_JSON}}', shellResult);
203
203
 
204
- const copyResult = await callAskLemurAPI({
204
+ const copyResult = await callAaiAPI({
205
205
  prompt: formattedCopyPrompt,
206
206
  context: copyPromptDetails.system || '',
207
207
  expectJson: false,
@@ -5,7 +5,7 @@ import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
5
5
  import ArrowPathRoundedSquareIcon from '@heroicons/react/24/outline/ArrowPathRoundedSquareIcon';
6
6
  import prompts from '@/constants/prompts.json';
7
7
  import { htmlToHtmlAst, cleanHtml } from '@/utils/compositor/htmlAst';
8
- import { callAskLemurAPI } from '@/utils/compositor/aiGeneration';
8
+ import { callAaiAPI } from '@/utils/compositor/aiGeneration';
9
9
  import type { TemplatePane } from '@/types/compositorTypes';
10
10
 
11
11
  interface AiRefineDesignStepProps {
@@ -54,7 +54,7 @@ export const AiRefineDesignStep = ({
54
54
  userPrompt = userPrompt.replace('{{HTML_INPUT}}', cleanHtml(initialHtml));
55
55
 
56
56
  // 1. Get RAW output from AI
57
- const resultHtml = await callAskLemurAPI({
57
+ const resultHtml = await callAaiAPI({
58
58
  prompt: userPrompt,
59
59
  context: systemPrompt,
60
60
  expectJson: false,