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,38 +1,12 @@
1
- import { Menu } from '@ark-ui/react';
1
+ import { useState } from 'react';
2
+ import { Dialog } from '@ark-ui/react/dialog';
2
3
  import { Portal } from '@ark-ui/react/portal';
3
4
  import ChevronDownIcon from '@heroicons/react/20/solid/ChevronDownIcon';
5
+ import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
4
6
  import { lispLexer } from '@/utils/actions/lispLexer';
5
7
  import { preParseAction } from '@/utils/actions/preParse_Action';
6
8
  import type { LispToken } from '@/types/compositorTypes';
7
9
 
8
- // CSS to style the menu items with hover and selection states
9
- const menuStyles = `
10
- .menu-content {
11
- transition-property: opacity, transform;
12
- transition-duration: 200ms;
13
- z-index: 10050;
14
- }
15
-
16
- .menu-content[data-state="open"] {
17
- opacity: 1;
18
- transform: translateY(0);
19
- }
20
-
21
- .menu-content[data-state="closed"] {
22
- opacity: 0;
23
- transform: translateY(1px);
24
- }
25
-
26
- .menu-item[data-highlighted] {
27
- background-color: #f3f4f6;
28
- }
29
-
30
- .menu-item:focus {
31
- outline: 2px solid #0891b2;
32
- outline-offset: -2px;
33
- }
34
- `;
35
-
36
10
  interface MenuLink {
37
11
  name: string;
38
12
  description: string;
@@ -63,6 +37,7 @@ interface MenuProps {
63
37
  const MenuComponent = (props: MenuProps) => {
64
38
  const { payload, slug, isContext, brandConfig } = props;
65
39
  const thisPayload = payload.optionsPayload;
40
+ const [mobileOpen, setMobileOpen] = useState(false);
66
41
 
67
42
  function processMenuLink(e: MenuLink): ProcessedMenuLinkDatum {
68
43
  const item = { ...e } as ProcessedMenuLinkDatum;
@@ -181,12 +156,112 @@ const MenuComponent = (props: MenuProps) => {
181
156
  );
182
157
  };
183
158
 
159
+ const MobileMenuItem = ({ item }: { item: ProcessedMenuLinkDatum }) => {
160
+ if (item.renderAs === 'button') {
161
+ return (
162
+ <button
163
+ type="button"
164
+ className="block w-full rounded-xl p-4 text-left hover:bg-mygreen/20 focus:outline-none focus:ring-2 focus:ring-myblue"
165
+ aria-label={`${item.name} - ${item.description}`}
166
+ hx-post="/api/v1/state"
167
+ hx-swap="none"
168
+ hx-vals={item.htmxVals}
169
+ onClick={() => setMobileOpen(false)}
170
+ >
171
+ <p className="text-xl font-bold leading-7 text-myblack">
172
+ {item.name}
173
+ </p>
174
+ <p className="mt-1 text-base leading-6 text-mydarkgrey">
175
+ {item.description}
176
+ </p>
177
+ </button>
178
+ );
179
+ }
180
+
181
+ if (item.renderAs === 'a') {
182
+ return (
183
+ <a
184
+ href={item.href}
185
+ className="block w-full rounded-xl p-4 text-left hover:bg-mygreen/20 focus:outline-none focus:ring-2 focus:ring-myblue"
186
+ aria-label={`${item.name} - ${item.description}`}
187
+ onClick={() => setMobileOpen(false)}
188
+ >
189
+ <p className="text-xl font-bold leading-7 text-myblack">
190
+ {item.name}
191
+ </p>
192
+ <p className="mt-1 text-base leading-6 text-mydarkgrey">
193
+ {item.description}
194
+ </p>
195
+ </a>
196
+ );
197
+ }
198
+
199
+ return (
200
+ <span
201
+ className="block w-full rounded-xl p-4 text-left opacity-60"
202
+ aria-label={`${item.name} - ${item.description}`}
203
+ >
204
+ <p className="text-xl font-bold leading-7 text-myblack">{item.name}</p>
205
+ <p className="mt-1 text-base leading-6 text-mydarkgrey">
206
+ {item.description}
207
+ </p>
208
+ </span>
209
+ );
210
+ };
211
+
212
+ const MobileCompactItem = ({ item }: { item: ProcessedMenuLinkDatum }) => {
213
+ if (item.renderAs === 'button') {
214
+ return (
215
+ <button
216
+ type="button"
217
+ className="block w-full rounded-lg p-3 text-left hover:bg-mygreen/20 focus:outline-none focus:ring-2 focus:ring-myblue"
218
+ title={item.description}
219
+ aria-label={`${item.name} - ${item.description}`}
220
+ hx-post="/api/v1/state"
221
+ hx-swap="none"
222
+ hx-vals={item.htmxVals}
223
+ onClick={() => setMobileOpen(false)}
224
+ >
225
+ <span className="block text-base font-bold leading-6 text-mydarkgrey">
226
+ {item.name}
227
+ </span>
228
+ </button>
229
+ );
230
+ }
231
+
232
+ if (item.renderAs === 'a') {
233
+ return (
234
+ <a
235
+ href={item.href}
236
+ className="block w-full rounded-lg p-3 text-left hover:bg-mygreen/20 focus:outline-none focus:ring-2 focus:ring-myblue"
237
+ title={item.description}
238
+ aria-label={`${item.name} - ${item.description}`}
239
+ onClick={() => setMobileOpen(false)}
240
+ >
241
+ <span className="block text-base font-bold leading-6 text-mydarkgrey">
242
+ {item.name}
243
+ </span>
244
+ </a>
245
+ );
246
+ }
247
+
248
+ return (
249
+ <span
250
+ className="block w-full rounded-lg p-3 text-left opacity-60"
251
+ title={item.description}
252
+ aria-label={`${item.name} - ${item.description}`}
253
+ >
254
+ <span className="block text-base font-bold leading-6 text-mydarkgrey">
255
+ {item.name}
256
+ </span>
257
+ </span>
258
+ );
259
+ };
260
+
184
261
  return (
185
262
  <>
186
- <style dangerouslySetInnerHTML={{ __html: menuStyles }} />
187
-
188
263
  {/* Desktop Navigation */}
189
- <nav className="ml-6 hidden flex-wrap items-center justify-end space-x-3 font-action md:flex md:space-x-6">
264
+ <nav className="ml-6 hidden min-w-0 max-w-full flex-wrap items-center justify-end space-x-3 font-action md:flex md:space-x-6">
190
265
  {featuredLinks.map((item: ProcessedMenuLinkDatum) => (
191
266
  <div key={item.name} className="relative py-1.5">
192
267
  <InteractiveMenuItem item={item} />
@@ -196,134 +271,81 @@ const MenuComponent = (props: MenuProps) => {
196
271
 
197
272
  {/* Mobile Navigation Menu */}
198
273
  <div className="font-action md:hidden">
199
- <Menu.Root>
200
- <Menu.Trigger
274
+ <Dialog.Root
275
+ open={mobileOpen}
276
+ onOpenChange={(details) => setMobileOpen(details.open)}
277
+ >
278
+ <Dialog.Trigger
201
279
  className="inline-flex rounded-md px-3 py-2 text-xl font-bold text-myblue hover:text-black focus:outline-none focus:ring-2 focus:ring-myblue"
202
280
  aria-label="Open navigation menu"
203
281
  >
204
282
  <span>MENU</span>
205
283
  <ChevronDownIcon className="ml-1 h-5 w-5" aria-hidden="true" />
206
- </Menu.Trigger>
284
+ </Dialog.Trigger>
207
285
 
208
286
  <Portal>
209
- <Menu.Positioner>
210
- <Menu.Content className="menu-content mt-5 flex">
211
- <div className="w-screen">
212
- <div className="text-md flex-auto overflow-hidden rounded-3xl bg-white p-4 leading-6 shadow-lg ring-1 ring-mydarkgrey/5">
213
- {/* Featured Links Section */}
214
- <div className="px-8">
287
+ <Dialog.Backdrop
288
+ className="fixed inset-0 bg-black/40"
289
+ style={{ zIndex: 10050 }}
290
+ />
291
+ <Dialog.Positioner
292
+ className="fixed inset-0"
293
+ style={{ zIndex: 10051 }}
294
+ >
295
+ <Dialog.Content className="h-full w-full overflow-hidden bg-white">
296
+ <div className="flex h-full flex-col overflow-hidden">
297
+ <div className="border-b border-mylightgrey px-4 py-3">
298
+ <div className="flex items-center justify-between gap-3">
299
+ <Dialog.Title className="text-lg font-bold text-myblack">
300
+ Navigation Menu
301
+ </Dialog.Title>
302
+ <Dialog.CloseTrigger
303
+ className="rounded-full p-2 text-mydarkgrey hover:bg-mylightgrey focus:outline-none focus:ring-2 focus:ring-myblue"
304
+ aria-label="Close navigation menu"
305
+ >
306
+ <XMarkIcon className="h-6 w-6" aria-hidden="true" />
307
+ </Dialog.CloseTrigger>
308
+ </div>
309
+ </div>
310
+
311
+ <div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-4 py-4">
312
+ <ul role="list" className="space-y-3">
215
313
  {featuredLinks.map((item: ProcessedMenuLinkDatum) => (
216
- <Menu.Item
217
- key={item.name}
218
- value={item.name}
219
- className="menu-item group relative flex gap-x-6 rounded-lg p-4 hover:bg-mygreen/20"
220
- >
221
- <div>
222
- {item.renderAs === 'button' ? (
223
- <button
224
- type="button"
225
- className="font-action text-xl text-myblack hover:text-black focus:text-black focus:outline-none"
226
- aria-label={`${item.name} - ${item.description}`}
227
- hx-post="/api/v1/state"
228
- hx-swap="none"
229
- hx-vals={item.htmxVals}
230
- >
231
- {item.name}
232
- <span className="absolute inset-0" />
233
- </button>
234
- ) : item.renderAs === 'a' ? (
235
- <a
236
- href={item.href}
237
- className="font-action text-xl text-myblack hover:text-black focus:text-black focus:outline-none"
238
- aria-label={`${item.name} - ${item.description}`}
239
- >
240
- {item.name}
241
- <span className="absolute inset-0" />
242
- </a>
243
- ) : (
244
- <span
245
- className="font-action text-xl text-myblack opacity-50"
246
- aria-label={`${item.name} - ${item.description}`}
247
- >
248
- {item.name}
249
- </span>
250
- )}
251
- <p className="mt-1 text-mydarkgrey">
252
- {item.description}
253
- </p>
254
- </div>
255
- </Menu.Item>
314
+ <li key={item.name} className="min-w-0">
315
+ <MobileMenuItem item={item} />
316
+ </li>
256
317
  ))}
257
- </div>
318
+ </ul>
258
319
 
259
- {/* Additional Links Section */}
260
320
  {additionalLinks.length > 0 && (
261
- <div className="bg-slate-50 p-8">
262
- <div className="flex justify-between">
263
- <h3
264
- className="mt-4 text-sm leading-6 text-myblue"
265
- id="additional-links-heading"
266
- >
267
- Additional Links
268
- </h3>
269
- </div>
321
+ <section className="mt-6 rounded-xl bg-slate-50 p-4">
322
+ <h3
323
+ className="text-sm font-bold leading-6 text-myblue"
324
+ id="additional-links-heading"
325
+ >
326
+ Additional Links
327
+ </h3>
270
328
  <ul
271
329
  role="list"
272
- className="mt-6 space-y-6"
330
+ className="mt-3 space-y-2"
273
331
  aria-labelledby="additional-links-heading"
274
332
  >
275
333
  {additionalLinks.map(
276
334
  (item: ProcessedMenuLinkDatum) => (
277
- <li key={item.name} className="relative">
278
- <Menu.Item
279
- value={item.name}
280
- className="menu-item block w-full text-left"
281
- >
282
- {item.renderAs === 'button' ? (
283
- <button
284
- type="button"
285
- className="block truncate rounded p-2 text-sm font-bold leading-6 text-mydarkgrey hover:text-black focus:text-black focus:underline focus:outline-none"
286
- title={item.description}
287
- aria-label={`${item.name} - ${item.description}`}
288
- hx-post="/api/v1/state"
289
- hx-swap="none"
290
- hx-vals={item.htmxVals}
291
- >
292
- {item.name}
293
- <span className="absolute inset-0" />
294
- </button>
295
- ) : item.renderAs === 'a' ? (
296
- <a
297
- href={item.href}
298
- className="block truncate rounded p-2 text-sm font-bold leading-6 text-mydarkgrey hover:text-black focus:text-black focus:underline focus:outline-none"
299
- title={item.description}
300
- aria-label={`${item.name} - ${item.description}`}
301
- >
302
- {item.name}
303
- <span className="absolute inset-0" />
304
- </a>
305
- ) : (
306
- <span
307
- className="block truncate rounded p-2 text-sm font-bold leading-6 text-mydarkgrey opacity-50"
308
- title={item.description}
309
- aria-label={`${item.name} - ${item.description}`}
310
- >
311
- {item.name}
312
- </span>
313
- )}
314
- </Menu.Item>
335
+ <li key={item.name} className="min-w-0">
336
+ <MobileCompactItem item={item} />
315
337
  </li>
316
338
  )
317
339
  )}
318
340
  </ul>
319
- </div>
341
+ </section>
320
342
  )}
321
343
  </div>
322
344
  </div>
323
- </Menu.Content>
324
- </Menu.Positioner>
345
+ </Dialog.Content>
346
+ </Dialog.Positioner>
325
347
  </Portal>
326
- </Menu.Root>
348
+ </Dialog.Root>
327
349
  </div>
328
350
  </>
329
351
  );
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, useRef } from 'react';
1
+ import { useState, useEffect, useRef, type FocusEvent } from 'react';
2
2
  import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
3
3
  import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
4
4
  import TrashIcon from '@heroicons/react/24/outline/TrashIcon';
@@ -162,7 +162,7 @@ const BunnyVideoSetup = ({ nodeId, params }: BunnyVideoSetupProps) => {
162
162
  };
163
163
 
164
164
  const saveChanges = (
165
- customChapters?: Chapter[] | React.FocusEvent<HTMLInputElement>
165
+ customChapters?: Chapter[] | FocusEvent<HTMLInputElement>
166
166
  ) => {
167
167
  if (!customChapters || 'target' in customChapters) {
168
168
  updatePaneNode();
@@ -2,7 +2,10 @@ import { useState, useEffect } from 'react';
2
2
  import { useStore } from '@nanostores/react';
3
3
  import { createListCollection } from '@ark-ui/react/collection';
4
4
  import { Select } from '@ark-ui/react/select';
5
- import { RadioGroup } from '@ark-ui/react/radio-group';
5
+ import {
6
+ RadioGroup,
7
+ useRadioGroupItemContext,
8
+ } from '@ark-ui/react/radio-group';
6
9
  import { Portal } from '@ark-ui/react/portal';
7
10
  import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
8
11
  import CheckCircleIcon from '@heroicons/react/24/outline/CheckCircleIcon';
@@ -49,6 +52,15 @@ interface AvailableFilter {
49
52
  values: string[];
50
53
  }
51
54
 
55
+ const VisitorTypeCheckIcon = () => {
56
+ const { checked } = useRadioGroupItemContext();
57
+ return (
58
+ <div className={`shrink-0 text-cyan-600 ${checked ? 'block' : 'hidden'}`}>
59
+ <CheckCircleIcon className="h-5 w-5" />
60
+ </div>
61
+ );
62
+ };
63
+
52
64
  interface EpinetDurationSelectorProps {
53
65
  fullContentMap?: ContentMapItem[];
54
66
  isLoading?: boolean;
@@ -382,6 +394,16 @@ const EpinetDurationSelector = ({
382
394
  @media (min-width: 641px) { .radio-item { flex: 1 1 calc(33.333% - 0.5rem); } }
383
395
  `;
384
396
 
397
+ const epinetUserSelectItemStyles = `
398
+ .epinet-user-select-item[data-highlighted] {
399
+ background-color: #0891b2;
400
+ color: #fff;
401
+ }
402
+ .epinet-user-select-item[data-highlighted] .text-gray-500 {
403
+ color: rgb(229 231 235);
404
+ }
405
+ `;
406
+
385
407
  const getFilterStatusMessage = () => {
386
408
  const needsApply = hasLocalChanges;
387
409
  const prefix = needsApply ? 'Press Apply Filters to load' : 'Showing';
@@ -546,6 +568,7 @@ const EpinetDurationSelector = ({
546
568
  <div className="duration-selector-container flex flex-col space-y-4 md:space-y-0">
547
569
  <div className="space-y-2">
548
570
  <style>{radioGroupStyles}</style>
571
+ <style>{epinetUserSelectItemStyles}</style>
549
572
  <RadioGroup.Root
550
573
  value={localFilters.visitorType}
551
574
  onValueChange={({ value }) =>
@@ -579,9 +602,7 @@ const EpinetDurationSelector = ({
579
602
  </div>
580
603
  </RadioGroup.ItemText>
581
604
  </div>
582
- <div className="hidden shrink-0 text-cyan-600 data-[state=checked]:block">
583
- <CheckCircleIcon className="h-5 w-5" />
584
- </div>
605
+ <VisitorTypeCheckIcon />
585
606
  </div>
586
607
  <RadioGroup.ItemHiddenInput />
587
608
  </RadioGroup.Item>
@@ -817,7 +838,7 @@ const EpinetDurationSelector = ({
817
838
  <Select.Item
818
839
  key="empty"
819
840
  item={{ value: '', label: 'Select user' }}
820
- className="cursor-pointer select-none p-2 text-sm text-gray-500 hover:bg-slate-100 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
841
+ className="epinet-user-select-item cursor-pointer select-none p-2 text-sm text-gray-500 hover:bg-slate-100"
821
842
  >
822
843
  <Select.ItemText>
823
844
  Select user
@@ -830,7 +851,7 @@ const EpinetDurationSelector = ({
830
851
  value: user.id,
831
852
  label: `${user.id} (${user.count} events)`,
832
853
  }}
833
- className="cursor-pointer select-none p-2 text-sm text-gray-700 hover:bg-slate-100 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
854
+ className="epinet-user-select-item cursor-pointer select-none p-2 text-sm text-gray-700 hover:bg-slate-100"
834
855
  >
835
856
  <Select.ItemText>
836
857
  {user.id}{' '}