astro-tractstack 2.3.1 → 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 (53) hide show
  1. package/dist/index.js +36 -3
  2. package/package.json +1 -1
  3. package/templates/custom/shopify/Cart.tsx +16 -5
  4. package/templates/custom/shopify/CheckoutModal.tsx +4 -4
  5. package/templates/custom/shopify/ShopifyCartManager.tsx +27 -36
  6. package/templates/custom/shopify/ShopifyCheckout.tsx +4 -33
  7. package/templates/custom/shopify/ShopifyProductGrid.tsx +42 -14
  8. package/templates/custom/shopify/ShopifyServiceList.tsx +94 -50
  9. package/templates/src/components/Footer.astro +2 -2
  10. package/templates/src/components/Header.astro +14 -8
  11. package/templates/src/components/Menu.tsx +157 -135
  12. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +2 -2
  13. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +27 -6
  14. package/templates/src/components/codehooks/EpinetTableView.tsx +153 -112
  15. package/templates/src/components/codehooks/EpinetWrapper.tsx +4 -1
  16. package/templates/src/components/codehooks/FeaturedArticleSetup.tsx +8 -1
  17. package/templates/src/components/codehooks/ProductCardSetup.tsx +9 -1
  18. package/templates/src/components/codehooks/ProductGridSetup.tsx +9 -1
  19. package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +2 -1
  20. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +1 -1
  21. package/templates/src/components/edit/ToolBar.tsx +2 -1
  22. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +2 -2
  23. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +13 -0
  24. package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +2 -2
  25. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +1 -1
  26. package/templates/src/components/edit/state/SaveModal.tsx +1 -1
  27. package/templates/src/components/edit/widgets/InteractiveDisclosureWidget.tsx +8 -3
  28. package/templates/src/components/form/DateTimeInput.tsx +10 -3
  29. package/templates/src/components/form/FileUpload.tsx +11 -5
  30. package/templates/src/components/form/NumberInput.tsx +2 -2
  31. package/templates/src/components/form/advanced/APIConfigSection.tsx +2 -38
  32. package/templates/src/components/form/brand/SiteConfigSection.tsx +10 -0
  33. package/templates/src/components/storykeep/Dashboard_Shopify.tsx +7 -8
  34. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +2 -2
  35. package/templates/src/components/storykeep/controls/content/ProductTable.tsx +2 -2
  36. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +79 -51
  37. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +1 -0
  38. package/templates/src/components/storykeep/email-builder/Blocks.tsx +169 -0
  39. package/templates/src/components/storykeep/email-builder/EmailBuilder.tsx +223 -0
  40. package/templates/src/components/storykeep/email-builder/PreviewModal.tsx +136 -0
  41. package/templates/src/components/storykeep/email-builder/PropertyPanel.tsx +154 -0
  42. package/templates/src/components/storykeep/shopify/ShopifyDashboard.tsx +1 -8
  43. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Bookings.tsx +32 -6
  44. package/templates/src/components/storykeep/shopify/ShopifyDashboard_Emails.tsx +105 -0
  45. package/templates/src/layouts/Layout.astro +8 -5
  46. package/templates/src/stores/shopify.ts +16 -0
  47. package/templates/src/types/formTypes.ts +4 -2
  48. package/templates/src/types/tractstack.ts +5 -2
  49. package/templates/src/utils/api/brandConfig.ts +2 -0
  50. package/templates/src/utils/api/brandHelpers.ts +16 -0
  51. package/templates/src/utils/api/emailHelpers.ts +105 -0
  52. package/templates/src/utils/tenantResolver.ts +1 -1
  53. package/utils/inject-files.ts +34 -1
@@ -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
@@ -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
 
@@ -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}
@@ -454,7 +454,7 @@ export default function SaveModal({
454
454
  Keep Editing
455
455
  </button>
456
456
  <a
457
- href="/storykeep/content"
457
+ href="/storykeep"
458
458
  className={`rounded bg-black px-4 py-2 text-white transition-colors hover:bg-white hover:text-black`}
459
459
  >
460
460
  Dashboard
@@ -71,7 +71,12 @@ const IconSelector = ({
71
71
  () => createListCollection({ items: filteredIcons }),
72
72
  [filteredIcons]
73
73
  );
74
- const iconSelectorStyles = `.icon-item .icon-indicator { display: none; } .icon-item[data-state="checked"] .icon-indicator { display: flex; }`;
74
+ const iconSelectorStyles = `
75
+ .icon-item .icon-indicator { display: none; }
76
+ .icon-item[data-state="checked"] .icon-indicator { display: flex; }
77
+ .icon-item[data-highlighted] { background-color: #0891b2; color: #fff; }
78
+ .icon-item[data-highlighted] .icon-indicator { color: #fff; }
79
+ `;
75
80
  return (
76
81
  <div>
77
82
  <style>{iconSelectorStyles}</style>
@@ -100,12 +105,12 @@ const IconSelector = ({
100
105
  <Combobox.Item
101
106
  key={icon}
102
107
  item={icon}
103
- className="icon-item relative cursor-pointer select-none py-2 pl-10 pr-4 text-gray-900 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
108
+ className="icon-item relative cursor-pointer select-none py-2 pl-10 pr-4 text-gray-900"
104
109
  >
105
110
  <Combobox.ItemText>
106
111
  <i className={`bi bi-${icon} mr-2 text-lg`}></i> {icon}
107
112
  </Combobox.ItemText>
108
- <Combobox.ItemIndicator className="icon-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600 data-[highlighted]:text-white">
113
+ <Combobox.ItemIndicator className="icon-indicator absolute inset-y-0 left-0 flex items-center pl-3 text-cyan-600">
109
114
  <CheckIcon className="h-5 w-5" />
110
115
  </Combobox.ItemIndicator>
111
116
  </Combobox.Item>
@@ -40,6 +40,12 @@ const ampmOptions = [
40
40
  { value: 'PM', label: 'PM' },
41
41
  ];
42
42
 
43
+ const datetimeSelectItemHighlightStyles = `
44
+ .datetime-select-item[data-highlighted] {
45
+ background-color: #eff6ff;
46
+ }
47
+ `;
48
+
43
49
  const DateTimeInput = ({
44
50
  value,
45
51
  onChange,
@@ -450,6 +456,7 @@ const DateTimeInput = ({
450
456
  {/* Time Picker Section (only show if withTime is true and not date-only) */}
451
457
  {withTime && displayFormat !== 'date' && (
452
458
  <div className="space-y-2">
459
+ <style>{datetimeSelectItemHighlightStyles}</style>
453
460
  <div className="text-xs font-bold text-gray-600">
454
461
  Time (Local Timezone)
455
462
  </div>
@@ -489,7 +496,7 @@ const DateTimeInput = ({
489
496
  <Select.Item
490
497
  key={option.value}
491
498
  item={option}
492
- className="flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50 data-[highlighted]:bg-blue-50"
499
+ className="datetime-select-item flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50"
493
500
  >
494
501
  <Select.ItemText>{option.label}</Select.ItemText>
495
502
  <Select.ItemIndicator>
@@ -540,7 +547,7 @@ const DateTimeInput = ({
540
547
  <Select.Item
541
548
  key={option.value}
542
549
  item={option}
543
- className="flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50 data-[highlighted]:bg-blue-50"
550
+ className="datetime-select-item flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50"
544
551
  >
545
552
  <Select.ItemText>{option.label}</Select.ItemText>
546
553
  <Select.ItemIndicator>
@@ -589,7 +596,7 @@ const DateTimeInput = ({
589
596
  <Select.Item
590
597
  key={option.value}
591
598
  item={option}
592
- className="flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50 data-[highlighted]:bg-blue-50"
599
+ className="datetime-select-item flex cursor-pointer items-center justify-between px-3 py-2 text-sm hover:bg-blue-50"
593
600
  >
594
601
  <Select.ItemText>{option.label}</Select.ItemText>
595
602
  <Select.ItemIndicator>
@@ -1,4 +1,10 @@
1
- import { useRef, useState, useId } from 'react';
1
+ import {
2
+ useRef,
3
+ useState,
4
+ useId,
5
+ type ChangeEvent,
6
+ type DragEvent,
7
+ } from 'react';
2
8
  import { classNames } from '@/utils/helpers';
3
9
  import {
4
10
  validateFile,
@@ -151,14 +157,14 @@ const FileUpload = ({
151
157
  }
152
158
  };
153
159
 
154
- const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
160
+ const handleFileSelect = (e: ChangeEvent<HTMLInputElement>) => {
155
161
  const file = e.target.files?.[0];
156
162
  if (file) {
157
163
  processFile(file);
158
164
  }
159
165
  };
160
166
 
161
- const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
167
+ const handleDrop = (e: DragEvent<HTMLDivElement>) => {
162
168
  e.preventDefault();
163
169
  setDragOver(false);
164
170
 
@@ -170,14 +176,14 @@ const FileUpload = ({
170
176
  }
171
177
  };
172
178
 
173
- const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
179
+ const handleDragOver = (e: DragEvent<HTMLDivElement>) => {
174
180
  e.preventDefault();
175
181
  if (!disabled) {
176
182
  setDragOver(true);
177
183
  }
178
184
  };
179
185
 
180
- const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
186
+ const handleDragLeave = (e: DragEvent<HTMLDivElement>) => {
181
187
  e.preventDefault();
182
188
  setDragOver(false);
183
189
  };