astro-tractstack 2.0.0-rc.0

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 (427) hide show
  1. package/LICENSE +110 -0
  2. package/README.md +56 -0
  3. package/astro.d.ts +64 -0
  4. package/bin/create-tractstack.js +483 -0
  5. package/dist/config.js +80 -0
  6. package/dist/index.js +2129 -0
  7. package/package.json +89 -0
  8. package/templates/artpacks/kCz/captainBreakfast_1080px.webp +0 -0
  9. package/templates/artpacks/kCz/captainBreakfast_1920px.webp +0 -0
  10. package/templates/artpacks/kCz/captainBreakfast_600px.webp +0 -0
  11. package/templates/artpacks/kCz/cleanDrips_1080px.webp +0 -0
  12. package/templates/artpacks/kCz/cleanDrips_1920px.webp +0 -0
  13. package/templates/artpacks/kCz/cleanDrips_600px.webp +0 -0
  14. package/templates/artpacks/kCz/crispwaves_1080px.webp +0 -0
  15. package/templates/artpacks/kCz/crispwaves_1920px.webp +0 -0
  16. package/templates/artpacks/kCz/crispwaves_600px.webp +0 -0
  17. package/templates/artpacks/kCz/dragonSkin_1080px.webp +0 -0
  18. package/templates/artpacks/kCz/dragonSkin_1920px.webp +0 -0
  19. package/templates/artpacks/kCz/dragonSkin_600px.webp +0 -0
  20. package/templates/artpacks/kCz/dragon_1080px.webp +0 -0
  21. package/templates/artpacks/kCz/dragon_1920px.webp +0 -0
  22. package/templates/artpacks/kCz/dragon_600px.webp +0 -0
  23. package/templates/artpacks/kCz/nightcity_1080px.webp +0 -0
  24. package/templates/artpacks/kCz/nightcity_1920px.webp +0 -0
  25. package/templates/artpacks/kCz/nightcity_600px.webp +0 -0
  26. package/templates/artpacks/kCz/pattern1_1080px.webp +0 -0
  27. package/templates/artpacks/kCz/pattern1_1920px.webp +0 -0
  28. package/templates/artpacks/kCz/pattern1_600px.webp +0 -0
  29. package/templates/artpacks/kCz/pattern2_1080px.webp +0 -0
  30. package/templates/artpacks/kCz/pattern2_1920px.webp +0 -0
  31. package/templates/artpacks/kCz/pattern2_600px.webp +0 -0
  32. package/templates/artpacks/kCz/skindrips_1080px.webp +0 -0
  33. package/templates/artpacks/kCz/skindrips_1920px.webp +0 -0
  34. package/templates/artpacks/kCz/skindrips_600px.webp +0 -0
  35. package/templates/artpacks/kCz/slimetime_1080px.webp +0 -0
  36. package/templates/artpacks/kCz/slimetime_1920px.webp +0 -0
  37. package/templates/artpacks/kCz/slimetime_600px.webp +0 -0
  38. package/templates/artpacks/kCz/snake_1080px.webp +0 -0
  39. package/templates/artpacks/kCz/snake_1920px.webp +0 -0
  40. package/templates/artpacks/kCz/snake_600px.webp +0 -0
  41. package/templates/artpacks/kCz/toxicshock_1080px.webp +0 -0
  42. package/templates/artpacks/kCz/toxicshock_1920px.webp +0 -0
  43. package/templates/artpacks/kCz/toxicshock_600px.webp +0 -0
  44. package/templates/artpacks/kCz/tractstack_1080px.webp +0 -0
  45. package/templates/artpacks/kCz/tractstack_1920px.webp +0 -0
  46. package/templates/artpacks/kCz/tractstack_600px.webp +0 -0
  47. package/templates/artpacks/kCz/tripdrips_1080px.webp +0 -0
  48. package/templates/artpacks/kCz/tripdrips_1920px.webp +0 -0
  49. package/templates/artpacks/kCz/tripdrips_600px.webp +0 -0
  50. package/templates/artpacks/kCz/wavedrips_1080px.webp +0 -0
  51. package/templates/artpacks/kCz/wavedrips_1920px.webp +0 -0
  52. package/templates/artpacks/kCz/wavedrips_600px.webp +0 -0
  53. package/templates/artpacks/t8k/beach_1080px.webp +0 -0
  54. package/templates/artpacks/t8k/beach_1920px.webp +0 -0
  55. package/templates/artpacks/t8k/beach_600px.webp +0 -0
  56. package/templates/artpacks/t8k/blast_1080px.webp +0 -0
  57. package/templates/artpacks/t8k/blast_1920px.webp +0 -0
  58. package/templates/artpacks/t8k/blast_600px.webp +0 -0
  59. package/templates/artpacks/t8k/bokeh_1080px.webp +0 -0
  60. package/templates/artpacks/t8k/bokeh_1920px.webp +0 -0
  61. package/templates/artpacks/t8k/bokeh_600px.webp +0 -0
  62. package/templates/artpacks/t8k/cartoon_1080px.webp +0 -0
  63. package/templates/artpacks/t8k/cartoon_1920px.webp +0 -0
  64. package/templates/artpacks/t8k/cartoon_600px.webp +0 -0
  65. package/templates/artpacks/t8k/darkeggshell_1080px.webp +0 -0
  66. package/templates/artpacks/t8k/darkeggshell_1920px.webp +0 -0
  67. package/templates/artpacks/t8k/darkeggshell_600px.webp +0 -0
  68. package/templates/artpacks/t8k/explosion_1080px.webp +0 -0
  69. package/templates/artpacks/t8k/explosion_1920px.webp +0 -0
  70. package/templates/artpacks/t8k/explosion_600px.webp +0 -0
  71. package/templates/artpacks/t8k/floral_1080px.webp +0 -0
  72. package/templates/artpacks/t8k/floral_1920px.webp +0 -0
  73. package/templates/artpacks/t8k/floral_600px.webp +0 -0
  74. package/templates/artpacks/t8k/flower_1080px.webp +0 -0
  75. package/templates/artpacks/t8k/flower_1920px.webp +0 -0
  76. package/templates/artpacks/t8k/flower_600px.webp +0 -0
  77. package/templates/artpacks/t8k/foliage_1080px.webp +0 -0
  78. package/templates/artpacks/t8k/foliage_1920px.webp +0 -0
  79. package/templates/artpacks/t8k/foliage_600px.webp +0 -0
  80. package/templates/artpacks/t8k/mist_1080px.webp +0 -0
  81. package/templates/artpacks/t8k/mist_1920px.webp +0 -0
  82. package/templates/artpacks/t8k/mist_600px.webp +0 -0
  83. package/templates/artpacks/t8k/portal_1080px.webp +0 -0
  84. package/templates/artpacks/t8k/portal_1920px.webp +0 -0
  85. package/templates/artpacks/t8k/portal_600px.webp +0 -0
  86. package/templates/artpacks/t8k/storytime_1080px.webp +0 -0
  87. package/templates/artpacks/t8k/storytime_1920px.webp +0 -0
  88. package/templates/artpacks/t8k/storytime_600px.webp +0 -0
  89. package/templates/artpacks/t8k/tacky_1080px.webp +0 -0
  90. package/templates/artpacks/t8k/tacky_1920px.webp +0 -0
  91. package/templates/artpacks/t8k/tacky_600px.webp +0 -0
  92. package/templates/artpacks/t8k/wallpaper_1080px.webp +0 -0
  93. package/templates/artpacks/t8k/wallpaper_1920px.webp +0 -0
  94. package/templates/artpacks/t8k/wallpaper_600px.webp +0 -0
  95. package/templates/brand/favicon.ico +0 -0
  96. package/templates/brand/logo.svg +19 -0
  97. package/templates/brand/static.jpg +0 -0
  98. package/templates/brand/wordmark.svg +4 -0
  99. package/templates/css/custom.css +51 -0
  100. package/templates/css/frontend.css +3519 -0
  101. package/templates/css/storykeep.css +92872 -0
  102. package/templates/custom/minimal/CodeHook.astro +53 -0
  103. package/templates/custom/minimal/CustomRoutes.astro +46 -0
  104. package/templates/custom/with-examples/CodeHook.astro +49 -0
  105. package/templates/custom/with-examples/CustomHero.astro +13 -0
  106. package/templates/custom/with-examples/CustomRoutes.astro +39 -0
  107. package/templates/custom/with-examples/pages/Collections.astro +110 -0
  108. package/templates/env.example +8 -0
  109. package/templates/fonts/Inter-Black.woff2 +0 -0
  110. package/templates/fonts/Inter-Bold.woff2 +0 -0
  111. package/templates/fonts/Inter-Regular.woff2 +0 -0
  112. package/templates/icons/h2.svg +1 -0
  113. package/templates/icons/h3.svg +1 -0
  114. package/templates/icons/h4.svg +1 -0
  115. package/templates/icons/h5.svg +1 -0
  116. package/templates/icons/image.svg +7 -0
  117. package/templates/icons/text.svg +6 -0
  118. package/templates/socials/codepen.svg +1 -0
  119. package/templates/socials/discord.svg +1 -0
  120. package/templates/socials/facebook.svg +1 -0
  121. package/templates/socials/github.svg +1 -0
  122. package/templates/socials/instagram.svg +1 -0
  123. package/templates/socials/linkedin.svg +1 -0
  124. package/templates/socials/mail.svg +1 -0
  125. package/templates/socials/rumble.svg +1 -0
  126. package/templates/socials/tiktok.svg +1 -0
  127. package/templates/socials/twitch.svg +1 -0
  128. package/templates/socials/twitter.svg +1 -0
  129. package/templates/socials/x.svg +1 -0
  130. package/templates/socials/youtube.svg +1 -0
  131. package/templates/src/client/analytics-events.ts +213 -0
  132. package/templates/src/client/belief-events.ts +205 -0
  133. package/templates/src/client/sse.ts +667 -0
  134. package/templates/src/components/Footer.astro +246 -0
  135. package/templates/src/components/Fragment.astro +70 -0
  136. package/templates/src/components/Header.astro +458 -0
  137. package/templates/src/components/Menu.tsx +196 -0
  138. package/templates/src/components/codehooks/BunnyVideoSetup.tsx +692 -0
  139. package/templates/src/components/codehooks/BunnyVideoWrapper.astro +78 -0
  140. package/templates/src/components/codehooks/EpinetDurationSelector.tsx +1020 -0
  141. package/templates/src/components/codehooks/EpinetTableView.tsx +594 -0
  142. package/templates/src/components/codehooks/EpinetWrapper.tsx +424 -0
  143. package/templates/src/components/codehooks/FeaturedContent.astro +273 -0
  144. package/templates/src/components/codehooks/FeaturedContentSetup.tsx +738 -0
  145. package/templates/src/components/codehooks/ListContent.astro +460 -0
  146. package/templates/src/components/codehooks/ListContentSetup.tsx +649 -0
  147. package/templates/src/components/codehooks/SankeyDiagram.tsx +359 -0
  148. package/templates/src/components/compositor/Compositor.tsx +144 -0
  149. package/templates/src/components/compositor/Node.tsx +415 -0
  150. package/templates/src/components/compositor/NodeWithGuid.tsx +25 -0
  151. package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +87 -0
  152. package/templates/src/components/compositor/elements/Belief.tsx +148 -0
  153. package/templates/src/components/compositor/elements/BgImage.tsx +118 -0
  154. package/templates/src/components/compositor/elements/BgVisualBreak.tsx +102 -0
  155. package/templates/src/components/compositor/elements/BunnyVideo.tsx +63 -0
  156. package/templates/src/components/compositor/elements/IdentifyAs.tsx +66 -0
  157. package/templates/src/components/compositor/elements/PlayButton.tsx +19 -0
  158. package/templates/src/components/compositor/elements/SignUp.tsx +179 -0
  159. package/templates/src/components/compositor/elements/Svg.tsx +33 -0
  160. package/templates/src/components/compositor/elements/ToggleBelief.tsx +36 -0
  161. package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +33 -0
  162. package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +35 -0
  163. package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +189 -0
  164. package/templates/src/components/compositor/nodes/Markdown.tsx +179 -0
  165. package/templates/src/components/compositor/nodes/Pane.tsx +277 -0
  166. package/templates/src/components/compositor/nodes/Pane_eraser.tsx +69 -0
  167. package/templates/src/components/compositor/nodes/Pane_layout.tsx +77 -0
  168. package/templates/src/components/compositor/nodes/RenderChildren.tsx +19 -0
  169. package/templates/src/components/compositor/nodes/StoryFragment.tsx +35 -0
  170. package/templates/src/components/compositor/nodes/TagElement.tsx +14 -0
  171. package/templates/src/components/compositor/nodes/Widget.tsx +115 -0
  172. package/templates/src/components/compositor/nodes/tagElements/NodeA.tsx +4 -0
  173. package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +26 -0
  174. package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +248 -0
  175. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +684 -0
  176. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +62 -0
  177. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +120 -0
  178. package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +62 -0
  179. package/templates/src/components/compositor/nodes/tagElements/NodeButton.tsx +5 -0
  180. package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +26 -0
  181. package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +28 -0
  182. package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +18 -0
  183. package/templates/src/components/compositor/nodes/tagElements/TabIndicator.tsx +51 -0
  184. package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +128 -0
  185. package/templates/src/components/compositor/preview/ListContentPreview.tsx +213 -0
  186. package/templates/src/components/compositor/preview/OgImagePreview.tsx +223 -0
  187. package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +199 -0
  188. package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +123 -0
  189. package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +154 -0
  190. package/templates/src/components/edit/Header.tsx +181 -0
  191. package/templates/src/components/edit/PanelSwitch.tsx +446 -0
  192. package/templates/src/components/edit/SettingsPanel.tsx +70 -0
  193. package/templates/src/components/edit/ToolBar.tsx +101 -0
  194. package/templates/src/components/edit/ToolMode.tsx +121 -0
  195. package/templates/src/components/edit/context/ContextPaneConfig.tsx +91 -0
  196. package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +174 -0
  197. package/templates/src/components/edit/context/ContextPaneConfig_title.tsx +186 -0
  198. package/templates/src/components/edit/pane/AddPanePanel.tsx +136 -0
  199. package/templates/src/components/edit/pane/AddPanePanel_break.tsx +470 -0
  200. package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +264 -0
  201. package/templates/src/components/edit/pane/AddPanePanel_new.tsx +623 -0
  202. package/templates/src/components/edit/pane/AddPanePanel_newAICopy.tsx +107 -0
  203. package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +217 -0
  204. package/templates/src/components/edit/pane/AddPanePanel_newCopyMode.tsx +109 -0
  205. package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +39 -0
  206. package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +445 -0
  207. package/templates/src/components/edit/pane/ConfigPanePanel.tsx +245 -0
  208. package/templates/src/components/edit/pane/PageGen.tsx +485 -0
  209. package/templates/src/components/edit/pane/PageGenSelector.tsx +238 -0
  210. package/templates/src/components/edit/pane/PageGenSpecial.tsx +362 -0
  211. package/templates/src/components/edit/pane/PageGen_preview.tsx +495 -0
  212. package/templates/src/components/edit/pane/PanePanel_impression.tsx +258 -0
  213. package/templates/src/components/edit/pane/PanePanel_path.tsx +268 -0
  214. package/templates/src/components/edit/pane/PanePanel_slug.tsx +219 -0
  215. package/templates/src/components/edit/pane/PanePanel_title.tsx +142 -0
  216. package/templates/src/components/edit/panels/StyleBreakPanel.tsx +182 -0
  217. package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +439 -0
  218. package/templates/src/components/edit/panels/StyleElementPanel.tsx +177 -0
  219. package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +349 -0
  220. package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +159 -0
  221. package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +320 -0
  222. package/templates/src/components/edit/panels/StyleImagePanel.tsx +460 -0
  223. package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +296 -0
  224. package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +153 -0
  225. package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +312 -0
  226. package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +273 -0
  227. package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +301 -0
  228. package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +132 -0
  229. package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +313 -0
  230. package/templates/src/components/edit/panels/StyleLinkPanel.tsx +346 -0
  231. package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +265 -0
  232. package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +240 -0
  233. package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +94 -0
  234. package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +110 -0
  235. package/templates/src/components/edit/panels/StyleParentPanel.tsx +263 -0
  236. package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +275 -0
  237. package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +112 -0
  238. package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +87 -0
  239. package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +141 -0
  240. package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +428 -0
  241. package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +292 -0
  242. package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +190 -0
  243. package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +152 -0
  244. package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +308 -0
  245. package/templates/src/components/edit/state/SaveModal.tsx +811 -0
  246. package/templates/src/components/edit/state/StylesMemory.tsx +310 -0
  247. package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +289 -0
  248. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +320 -0
  249. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +888 -0
  250. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +269 -0
  251. package/templates/src/components/edit/storyfragment/StoryFragmentPanel_title.tsx +190 -0
  252. package/templates/src/components/edit/widgets/BeliefWidget.tsx +183 -0
  253. package/templates/src/components/edit/widgets/BunnyWidget.tsx +134 -0
  254. package/templates/src/components/edit/widgets/IdentifyAsWidget.tsx +193 -0
  255. package/templates/src/components/edit/widgets/SignupWidget.tsx +177 -0
  256. package/templates/src/components/edit/widgets/ToggleWidget.tsx +152 -0
  257. package/templates/src/components/edit/widgets/YouTubeWidget.tsx +65 -0
  258. package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +353 -0
  259. package/templates/src/components/fields/ArtpackImage.tsx +480 -0
  260. package/templates/src/components/fields/BackgroundImage.tsx +530 -0
  261. package/templates/src/components/fields/BackgroundImageWrapper.tsx +192 -0
  262. package/templates/src/components/fields/BooleanParam.tsx +67 -0
  263. package/templates/src/components/fields/BunnyMomentSelector.tsx +56 -0
  264. package/templates/src/components/fields/ColorPickerCombo.tsx +284 -0
  265. package/templates/src/components/fields/ImageUpload.tsx +405 -0
  266. package/templates/src/components/fields/MultiParam.tsx +75 -0
  267. package/templates/src/components/fields/PaneBreakCollectionSelector.tsx +97 -0
  268. package/templates/src/components/fields/PaneBreakShapeSelector.tsx +134 -0
  269. package/templates/src/components/fields/SelectedTailwindClass.tsx +44 -0
  270. package/templates/src/components/fields/SingleParam.tsx +73 -0
  271. package/templates/src/components/fields/ViewportComboBox.tsx +252 -0
  272. package/templates/src/components/form/ActionBuilderField.tsx +282 -0
  273. package/templates/src/components/form/ActionBuilderSlugSelector.tsx +182 -0
  274. package/templates/src/components/form/BooleanToggle.tsx +94 -0
  275. package/templates/src/components/form/ColorPicker.tsx +153 -0
  276. package/templates/src/components/form/DateTimeInput.tsx +638 -0
  277. package/templates/src/components/form/EnumSelect.tsx +88 -0
  278. package/templates/src/components/form/FileUpload.tsx +465 -0
  279. package/templates/src/components/form/MagicPathBuilder.tsx +546 -0
  280. package/templates/src/components/form/NumberInput.tsx +101 -0
  281. package/templates/src/components/form/ParagraphArrayInput.tsx +207 -0
  282. package/templates/src/components/form/StringArrayInput.tsx +163 -0
  283. package/templates/src/components/form/StringInput.tsx +88 -0
  284. package/templates/src/components/form/UnsavedChangesBar.tsx +295 -0
  285. package/templates/src/components/form/advanced/APIConfigSection.tsx +69 -0
  286. package/templates/src/components/form/advanced/AuthConfigSection.tsx +97 -0
  287. package/templates/src/components/form/brand/BrandAssetsSection.tsx +93 -0
  288. package/templates/src/components/form/brand/BrandColorsSection.tsx +201 -0
  289. package/templates/src/components/form/brand/SEOSection.tsx +101 -0
  290. package/templates/src/components/form/brand/SiteConfigSection.tsx +61 -0
  291. package/templates/src/components/form/brand/SocialLinksSection.tsx +393 -0
  292. package/templates/src/components/profile/ProfileConsent.tsx +65 -0
  293. package/templates/src/components/profile/ProfileCreate.tsx +462 -0
  294. package/templates/src/components/profile/ProfileEdit.tsx +409 -0
  295. package/templates/src/components/profile/ProfileSwitch.tsx +255 -0
  296. package/templates/src/components/profile/ProfileUnlock.tsx +221 -0
  297. package/templates/src/components/storykeep/Dashboard.tsx +160 -0
  298. package/templates/src/components/storykeep/Dashboard_Activity.tsx +56 -0
  299. package/templates/src/components/storykeep/Dashboard_Advanced.tsx +165 -0
  300. package/templates/src/components/storykeep/Dashboard_Analytics.tsx +451 -0
  301. package/templates/src/components/storykeep/Dashboard_Branding.tsx +95 -0
  302. package/templates/src/components/storykeep/Dashboard_Content.tsx +191 -0
  303. package/templates/src/components/storykeep/controls/UsageCell.tsx +71 -0
  304. package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +378 -0
  305. package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +329 -0
  306. package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +385 -0
  307. package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +149 -0
  308. package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +397 -0
  309. package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +260 -0
  310. package/templates/src/components/storykeep/controls/content/ManageContent.tsx +439 -0
  311. package/templates/src/components/storykeep/controls/content/MenuForm.tsx +239 -0
  312. package/templates/src/components/storykeep/controls/content/MenuTable.tsx +332 -0
  313. package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +724 -0
  314. package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +355 -0
  315. package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +222 -0
  316. package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +482 -0
  317. package/templates/src/components/storykeep/state/BrandingWrapper.tsx +42 -0
  318. package/templates/src/components/storykeep/state/FetchAnalytics.tsx +350 -0
  319. package/templates/src/components/storykeep/widgets/ResponsiveLine.tsx +319 -0
  320. package/templates/src/components/storykeep/widgets/Wizard.tsx +278 -0
  321. package/templates/src/components/tenant/RegistrationForm.tsx +447 -0
  322. package/templates/src/components/widgets/BunnyVideoHero.astro +775 -0
  323. package/templates/src/components/widgets/Impression.tsx +102 -0
  324. package/templates/src/components/widgets/ImpressionWrapper.tsx +214 -0
  325. package/templates/src/constants/beliefs.ts +61 -0
  326. package/templates/src/constants/brandThemes.ts +133 -0
  327. package/templates/src/constants/prompts.json +55 -0
  328. package/templates/src/constants/shapes.ts +556 -0
  329. package/templates/src/constants/stopWords.ts +116 -0
  330. package/templates/src/constants/tailwindColors.json +344 -0
  331. package/templates/src/constants.ts +274 -0
  332. package/templates/src/hooks/useFormState.ts +203 -0
  333. package/templates/src/layouts/Layout.astro +290 -0
  334. package/templates/src/lib/session.ts +126 -0
  335. package/templates/src/lib/storyData.ts +56 -0
  336. package/templates/src/middleware.ts +52 -0
  337. package/templates/src/pages/404.astro +54 -0
  338. package/templates/src/pages/[...slug]/edit.astro +216 -0
  339. package/templates/src/pages/[...slug].astro +148 -0
  340. package/templates/src/pages/api/auth/decode.ts +101 -0
  341. package/templates/src/pages/api/auth/login.ts +122 -0
  342. package/templates/src/pages/api/auth/logout.ts +37 -0
  343. package/templates/src/pages/api/auth/profile.ts +76 -0
  344. package/templates/src/pages/api/orphan-analysis.ts +106 -0
  345. package/templates/src/pages/api/tailwind.ts +116 -0
  346. package/templates/src/pages/collections/[param1].astro +65 -0
  347. package/templates/src/pages/context/[...contextSlug]/edit.astro +207 -0
  348. package/templates/src/pages/context/[...contextSlug].astro +161 -0
  349. package/templates/src/pages/llms.txt.ts +122 -0
  350. package/templates/src/pages/maint.astro +183 -0
  351. package/templates/src/pages/media/[...slug].astro +67 -0
  352. package/templates/src/pages/robots.txt.ts +36 -0
  353. package/templates/src/pages/sandbox/activate.astro +258 -0
  354. package/templates/src/pages/sandbox/register.astro +44 -0
  355. package/templates/src/pages/sandbox/success.astro +179 -0
  356. package/templates/src/pages/sitemap.xml.ts +119 -0
  357. package/templates/src/pages/storykeep/advanced.astro +69 -0
  358. package/templates/src/pages/storykeep/branding.astro +57 -0
  359. package/templates/src/pages/storykeep/content.astro +71 -0
  360. package/templates/src/pages/storykeep/init.astro +36 -0
  361. package/templates/src/pages/storykeep/login.astro +266 -0
  362. package/templates/src/pages/storykeep/logout.astro +84 -0
  363. package/templates/src/pages/storykeep/profile.astro +98 -0
  364. package/templates/src/pages/storykeep.astro +81 -0
  365. package/templates/src/stores/analytics.ts +171 -0
  366. package/templates/src/stores/backend.ts +16 -0
  367. package/templates/src/stores/navigation.ts +149 -0
  368. package/templates/src/stores/nodes.ts +2390 -0
  369. package/templates/src/stores/nodesHistory.ts +85 -0
  370. package/templates/src/stores/notificationSystem.ts +41 -0
  371. package/templates/src/stores/orphanAnalysis.ts +409 -0
  372. package/templates/src/stores/storykeep.ts +247 -0
  373. package/templates/src/types/astro.ts +86 -0
  374. package/templates/src/types/compositorTypes.ts +456 -0
  375. package/templates/src/types/formTypes.ts +281 -0
  376. package/templates/src/types/multiTenant.ts +77 -0
  377. package/templates/src/types/nodeProps.ts +66 -0
  378. package/templates/src/types/tractstack.ts +445 -0
  379. package/templates/src/utils/aai/getTitleSlug.ts +72 -0
  380. package/templates/src/utils/actions/actionButton.ts +101 -0
  381. package/templates/src/utils/actions/lispLexer.ts +57 -0
  382. package/templates/src/utils/actions/preParse_Action.ts +85 -0
  383. package/templates/src/utils/actions/preParse_Bunny.ts +50 -0
  384. package/templates/src/utils/actions/preParse_Clicked.ts +87 -0
  385. package/templates/src/utils/actions/preParse_Impression.ts +71 -0
  386. package/templates/src/utils/api/advancedConfig.ts +66 -0
  387. package/templates/src/utils/api/advancedHelpers.ts +134 -0
  388. package/templates/src/utils/api/beliefConfig.ts +87 -0
  389. package/templates/src/utils/api/beliefHelpers.ts +196 -0
  390. package/templates/src/utils/api/brandConfig.ts +126 -0
  391. package/templates/src/utils/api/brandHelpers.ts +155 -0
  392. package/templates/src/utils/api/fileHelpers.ts +306 -0
  393. package/templates/src/utils/api/menuConfig.ts +57 -0
  394. package/templates/src/utils/api/menuHelpers.ts +156 -0
  395. package/templates/src/utils/api/resourceConfig.ts +158 -0
  396. package/templates/src/utils/api/resourceHelpers.ts +72 -0
  397. package/templates/src/utils/api/tenantConfig.ts +97 -0
  398. package/templates/src/utils/api/tenantHelpers.ts +172 -0
  399. package/templates/src/utils/api.ts +183 -0
  400. package/templates/src/utils/auth.ts +150 -0
  401. package/templates/src/utils/backend.ts +243 -0
  402. package/templates/src/utils/compositor/TemplateMarkdowns.ts +118 -0
  403. package/templates/src/utils/compositor/TemplateNodes.ts +138 -0
  404. package/templates/src/utils/compositor/TemplatePanes.ts +100 -0
  405. package/templates/src/utils/compositor/allowInsert.ts +100 -0
  406. package/templates/src/utils/compositor/domHelpers.ts +37 -0
  407. package/templates/src/utils/compositor/handleClickEvent.ts +131 -0
  408. package/templates/src/utils/compositor/nodesHelper.ts +491 -0
  409. package/templates/src/utils/compositor/nodesMarkdownGenerator.ts +292 -0
  410. package/templates/src/utils/compositor/processMarkdown.ts +431 -0
  411. package/templates/src/utils/compositor/reduceNodesClassNames.ts +192 -0
  412. package/templates/src/utils/compositor/tailwindClasses.ts +1795 -0
  413. package/templates/src/utils/compositor/tailwindColors.ts +227 -0
  414. package/templates/src/utils/compositor/templateMarkdownStyles.ts +1265 -0
  415. package/templates/src/utils/compositor/typeGuards.ts +193 -0
  416. package/templates/src/utils/etl/extractor.ts +119 -0
  417. package/templates/src/utils/etl/index.ts +88 -0
  418. package/templates/src/utils/etl/loader.ts +36 -0
  419. package/templates/src/utils/etl/transformer.ts +286 -0
  420. package/templates/src/utils/helpers.ts +435 -0
  421. package/templates/src/utils/layout.ts +209 -0
  422. package/templates/src/utils/profileStorage.ts +306 -0
  423. package/templates/src/utils/useInterval.ts +27 -0
  424. package/templates/tailwind.config.cjs +169 -0
  425. package/utils/create-resolver.ts +10 -0
  426. package/utils/inject-files.ts +2140 -0
  427. package/utils/validate-config.ts +43 -0
@@ -0,0 +1,1020 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useStore } from '@nanostores/react';
3
+ import { createListCollection } from '@ark-ui/react/collection';
4
+ import { Select } from '@ark-ui/react/select';
5
+ import { RadioGroup } from '@ark-ui/react/radio-group';
6
+ import { Portal } from '@ark-ui/react/portal';
7
+ import CheckCircleIcon from '@heroicons/react/24/outline/CheckCircleIcon';
8
+ import ChevronLeftIcon from '@heroicons/react/24/outline/ChevronLeftIcon';
9
+ import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon';
10
+ import { epinetCustomFilters } from '@/stores/analytics';
11
+ import EpinetTableView from './EpinetTableView';
12
+ import { MAX_ANALYTICS_HOURS } from '@/constants';
13
+
14
+ const getHourOptions = () => {
15
+ const hours = Array.from({ length: 24 }, (_, i) => ({
16
+ value: i.toString().padStart(2, '0'),
17
+ label: `${i.toString().padStart(2, '0')}:00`,
18
+ sortOrder: i,
19
+ }));
20
+ hours.push({ value: '24', label: '23:59', sortOrder: 24 });
21
+ hours.sort((a, b) => a.sortOrder - b.sortOrder);
22
+ return hours.map(({ value, label }) => ({ value, label }));
23
+ };
24
+
25
+ const getEndHourOptions = () => {
26
+ const hours = Array.from({ length: 24 }, (_, i) => ({
27
+ value: i.toString().padStart(2, '0'),
28
+ label: `${i.toString().padStart(2, '0')}:59`, // Always show :59 for end hours
29
+ sortOrder: i,
30
+ }));
31
+ hours.push({ value: '24', label: '23:59', sortOrder: 24 });
32
+ hours.sort((a, b) => a.sortOrder - b.sortOrder);
33
+ return hours.map(({ value, label }) => ({ value, label }));
34
+ };
35
+
36
+ const hourOptions = getHourOptions();
37
+ const endHourOptions = getEndHourOptions();
38
+
39
+ interface ContentMapItem {
40
+ id: string;
41
+ title: string;
42
+ type: string;
43
+ }
44
+
45
+ interface EpinetDurationSelectorProps {
46
+ fullContentMap?: ContentMapItem[];
47
+ isLoading?: boolean;
48
+ }
49
+
50
+ const EpinetDurationSelector = ({
51
+ fullContentMap,
52
+ isLoading,
53
+ }: EpinetDurationSelectorProps = {}) => {
54
+ const [startDate, setStartDate] = useState<Date | null>(null);
55
+ const [endDate, setEndDate] = useState<Date | null>(null);
56
+ const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
57
+ const [currentUserPage, setCurrentUserPage] = useState(0);
58
+ const [hasLocalChanges, setHasLocalChanges] = useState(false);
59
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
60
+ const [isApplying, setIsApplying] = useState(false);
61
+ const [isVisible, setIsVisible] = useState(false);
62
+ const [isAnimating, setIsAnimating] = useState(false);
63
+ const [showTable, setShowTable] = useState(false);
64
+ const usersPerPage = 50;
65
+
66
+ const $epinetCustomFilters = useStore(epinetCustomFilters);
67
+
68
+ const [localFilters, setLocalFilters] = useState({
69
+ visitorType: $epinetCustomFilters.visitorType || 'all',
70
+ selectedUserId: $epinetCustomFilters.selectedUserId || null,
71
+ startHour: '00',
72
+ endHour: '23:59',
73
+ });
74
+
75
+ useEffect(() => {
76
+ const now = new Date();
77
+ const endDate = new Date(now);
78
+ let startDate = new Date(endDate);
79
+ startDate.setDate(startDate.getDate() - 7); // Default to one week
80
+
81
+ setStartDate(startDate);
82
+ setEndDate(endDate);
83
+ const currentHour = now.getHours().toString().padStart(2, '0');
84
+ setLocalFilters((prev) => ({
85
+ ...prev,
86
+ startHour: currentHour,
87
+ endHour: currentHour,
88
+ }));
89
+ }, []);
90
+
91
+ // MODIFIED: Enhanced sync from store
92
+ useEffect(() => {
93
+ if ($epinetCustomFilters.startTimeUTC && $epinetCustomFilters.endTimeUTC) {
94
+ const startUTC = new Date($epinetCustomFilters.startTimeUTC);
95
+ const endUTC = new Date($epinetCustomFilters.endTimeUTC);
96
+
97
+ // Update local dates and hours to match store
98
+ setStartDate(
99
+ new Date(
100
+ startUTC.getFullYear(),
101
+ startUTC.getMonth(),
102
+ startUTC.getDate()
103
+ )
104
+ );
105
+ setEndDate(
106
+ new Date(endUTC.getFullYear(), endUTC.getMonth(), endUTC.getDate())
107
+ );
108
+
109
+ setLocalFilters((prev) => ({
110
+ ...prev,
111
+ startHour: startUTC.getHours().toString().padStart(2, '0'),
112
+ endHour: endUTC.getHours().toString().padStart(2, '0'),
113
+ visitorType: $epinetCustomFilters.visitorType || 'all',
114
+ selectedUserId: $epinetCustomFilters.selectedUserId,
115
+ }));
116
+
117
+ setHasLocalChanges(false);
118
+ } else {
119
+ setLocalFilters((prev) => ({
120
+ ...prev,
121
+ visitorType: $epinetCustomFilters.visitorType || 'all',
122
+ selectedUserId: $epinetCustomFilters.selectedUserId,
123
+ }));
124
+ }
125
+ }, [$epinetCustomFilters]);
126
+
127
+ useEffect(() => {
128
+ setCurrentUserPage(0);
129
+ }, [localFilters.visitorType]);
130
+
131
+ // Handle UnsavedChangesBar-style visibility animations
132
+ useEffect(() => {
133
+ if (hasLocalChanges && !isVisible) {
134
+ setIsVisible(true);
135
+ setTimeout(() => setIsAnimating(true), 10); // Trigger entrance animation
136
+ } else if (!hasLocalChanges && isVisible && !isApplying) {
137
+ setIsAnimating(false);
138
+ setTimeout(() => setIsVisible(false), 300); // Brief delay before hiding
139
+ }
140
+ }, [hasLocalChanges, isVisible, isApplying]);
141
+
142
+ const visitorTypes = [
143
+ { id: 'all', title: 'All Traffic', description: 'All visitors' },
144
+ { id: 'anonymous', title: 'Anonymous', description: 'Anonymous visitors' },
145
+ { id: 'known', title: 'Known Leads', description: 'Known visitors' },
146
+ ] as const;
147
+
148
+ const updateVisitorType = (type: 'all' | 'anonymous' | 'known') => {
149
+ setLocalFilters((prev) => ({
150
+ ...prev,
151
+ visitorType: type,
152
+ selectedUserId: null,
153
+ }));
154
+ setHasLocalChanges(true);
155
+ };
156
+
157
+ const updateSelectedUser = (userId: string | null) => {
158
+ setLocalFilters((prev) => ({ ...prev, selectedUserId: userId }));
159
+ setHasLocalChanges(true);
160
+ };
161
+
162
+ const updateHour = (type: 'startHour' | 'endHour', hour: string) => {
163
+ setLocalFilters((prev) => ({ ...prev, [type]: hour }));
164
+ setHasLocalChanges(true);
165
+ setErrorMessage(null);
166
+ };
167
+
168
+ // Create UTC datetime from local date and hour selection
169
+ const createUTCDateTime = (date: Date, hourStr: string): Date => {
170
+ const localDateTime = new Date(date);
171
+ const hour = hourStr === '24' ? 23 : parseInt(hourStr);
172
+ const minute = hourStr === '24' ? 59 : 0;
173
+ localDateTime.setHours(hour, minute, 0, 0);
174
+
175
+ // Convert to UTC by creating new Date with UTC values
176
+ return new Date(localDateTime.getTime());
177
+ };
178
+
179
+ const updateDateRange = async () => {
180
+ if (!startDate || !endDate) {
181
+ setErrorMessage('Please select both start and end dates.');
182
+ return;
183
+ }
184
+
185
+ const startUTCTime = createUTCDateTime(startDate, localFilters.startHour);
186
+ const endUTCTime = createUTCDateTime(endDate, localFilters.endHour);
187
+
188
+ if (endUTCTime <= startUTCTime) {
189
+ setErrorMessage('End time must be after start time.');
190
+ return;
191
+ }
192
+
193
+ const nowUTC = new Date();
194
+ const maxPastTime = new Date(
195
+ nowUTC.getTime() - MAX_ANALYTICS_HOURS * 60 * 60 * 1000
196
+ );
197
+
198
+ if (startUTCTime < maxPastTime) {
199
+ setErrorMessage(
200
+ `Start time cannot be more than ${MAX_ANALYTICS_HOURS} hours in the past.`
201
+ );
202
+ return;
203
+ }
204
+
205
+ if (endUTCTime > nowUTC) {
206
+ setErrorMessage('End time cannot be in the future.');
207
+ return;
208
+ }
209
+
210
+ setIsApplying(true);
211
+
212
+ try {
213
+ // Update with UTC timestamps
214
+ epinetCustomFilters.set(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
215
+ ...$epinetCustomFilters,
216
+ visitorType: localFilters.visitorType,
217
+ selectedUserId: localFilters.selectedUserId,
218
+ startTimeUTC: startUTCTime.toISOString(),
219
+ endTimeUTC: endUTCTime.toISOString(),
220
+ });
221
+
222
+ setHasLocalChanges(false);
223
+ setErrorMessage(null);
224
+
225
+ // Brief delay to show success state
226
+ setTimeout(() => {
227
+ setIsApplying(false);
228
+ }, 1000);
229
+ } catch (error) {
230
+ setIsApplying(false);
231
+ setErrorMessage('Failed to apply filters. Please try again.');
232
+ }
233
+ };
234
+
235
+ const handleDateChange = (
236
+ type: 'start' | 'end',
237
+ dateValue: string | null
238
+ ) => {
239
+ if (!dateValue) {
240
+ setErrorMessage('Please select a valid date.');
241
+ return;
242
+ }
243
+
244
+ const newDate = new Date(
245
+ parseInt(dateValue.split('-')[0]),
246
+ parseInt(dateValue.split('-')[1]) - 1,
247
+ parseInt(dateValue.split('-')[2])
248
+ );
249
+
250
+ const nowUTC = new Date();
251
+ const maxPastTime = new Date(
252
+ nowUTC.getTime() - MAX_ANALYTICS_HOURS * 60 * 60 * 1000
253
+ );
254
+
255
+ if (newDate < maxPastTime) {
256
+ setErrorMessage(
257
+ `Date cannot be more than ${MAX_ANALYTICS_HOURS} hours in the past.`
258
+ );
259
+ return;
260
+ }
261
+
262
+ if (type === 'start') {
263
+ setStartDate(newDate);
264
+ } else {
265
+ setEndDate(newDate);
266
+ }
267
+
268
+ setHasLocalChanges(true);
269
+ setErrorMessage(null);
270
+ };
271
+
272
+ const formatDateDisplay = (date: Date | null) => {
273
+ if (!date) return 'Select date';
274
+ return date.toLocaleDateString(undefined, {
275
+ year: 'numeric',
276
+ month: 'short',
277
+ day: 'numeric',
278
+ });
279
+ };
280
+
281
+ const formatDateHourDisplay = (
282
+ date: Date,
283
+ hourStr: string,
284
+ isEndTime = false
285
+ ) => {
286
+ if (!date) return '';
287
+
288
+ const localDateTime = new Date(date);
289
+ const hour = hourStr === '24' ? 23 : parseInt(hourStr);
290
+ // For end times, always show :59 to represent the full hour
291
+ const minute = isEndTime ? 59 : hourStr === '24' ? 59 : 0;
292
+ localDateTime.setHours(hour, minute, 0, 0);
293
+
294
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
295
+
296
+ return (
297
+ localDateTime.toLocaleString(undefined, {
298
+ year: 'numeric',
299
+ month: '2-digit',
300
+ day: '2-digit',
301
+ hour: '2-digit',
302
+ minute: '2-digit',
303
+ hour12: true,
304
+ timeZone,
305
+ }) + ` (${timeZone})`
306
+ );
307
+ };
308
+
309
+ // Display current UTC range in local timezone with proper end time formatting
310
+ const formatCurrentUTCRange = () => {
311
+ const { startTimeUTC, endTimeUTC } = $epinetCustomFilters;
312
+ if (!startTimeUTC || !endTimeUTC) return '';
313
+
314
+ const startLocal = new Date(startTimeUTC);
315
+ const endLocal = new Date(endTimeUTC);
316
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
317
+
318
+ const formatTime = (date: Date, isEndTime = false) => {
319
+ // For end times, always show :59 for presentation
320
+ const displayDate = isEndTime ? new Date(date.getTime()) : date;
321
+ if (isEndTime) {
322
+ displayDate.setMinutes(59);
323
+ }
324
+
325
+ return displayDate.toLocaleString(undefined, {
326
+ year: 'numeric',
327
+ month: '2-digit',
328
+ day: '2-digit',
329
+ hour: '2-digit',
330
+ minute: '2-digit',
331
+ hour12: true,
332
+ timeZone,
333
+ });
334
+ };
335
+
336
+ return `${formatTime(startLocal, false)} to ${formatTime(endLocal, true)} (${timeZone})`;
337
+ };
338
+
339
+ const paginatedUserCounts = ($epinetCustomFilters.userCounts || []).slice(
340
+ currentUserPage * usersPerPage,
341
+ (currentUserPage + 1) * usersPerPage
342
+ );
343
+
344
+ const totalUserPages = Math.ceil(
345
+ (($epinetCustomFilters.userCounts || []).length || 0) / usersPerPage
346
+ );
347
+
348
+ const nextUserPage = () => {
349
+ if (currentUserPage < totalUserPages - 1)
350
+ setCurrentUserPage(currentUserPage + 1);
351
+ };
352
+
353
+ const prevUserPage = () => {
354
+ if (currentUserPage > 0) setCurrentUserPage(currentUserPage - 1);
355
+ };
356
+
357
+ const maxDate = new Date();
358
+ const minDate = new Date();
359
+ minDate.setHours(minDate.getHours() - MAX_ANALYTICS_HOURS);
360
+
361
+ const setPresetDateRange = (period: string) => {
362
+ const nowUTC = new Date();
363
+ let hoursBack: number;
364
+
365
+ if (period === '24h') {
366
+ hoursBack = 24;
367
+ } else if (period === '7d') {
368
+ hoursBack = 168;
369
+ } else if (period === '28d') {
370
+ hoursBack = 672;
371
+ } else {
372
+ return;
373
+ }
374
+
375
+ const startTimeUTC = new Date(
376
+ nowUTC.getTime() - hoursBack * 60 * 60 * 1000
377
+ );
378
+
379
+ // Convert to local dates and set local state
380
+ const startLocal = new Date(startTimeUTC);
381
+ const endLocal = new Date(nowUTC);
382
+
383
+ setStartDate(
384
+ new Date(
385
+ startLocal.getFullYear(),
386
+ startLocal.getMonth(),
387
+ startLocal.getDate()
388
+ )
389
+ );
390
+ setEndDate(
391
+ new Date(endLocal.getFullYear(), endLocal.getMonth(), endLocal.getDate())
392
+ );
393
+
394
+ setLocalFilters((prev) => ({
395
+ ...prev,
396
+ startHour: startLocal.getHours().toString().padStart(2, '0'),
397
+ endHour: endLocal.getHours().toString().padStart(2, '0'),
398
+ }));
399
+
400
+ setHasLocalChanges(true);
401
+ setIsDatePickerOpen(false);
402
+ setErrorMessage(null);
403
+ };
404
+
405
+ const cancelChanges = () => {
406
+ const now = new Date();
407
+ setLocalFilters({
408
+ visitorType: $epinetCustomFilters.visitorType || 'all',
409
+ selectedUserId: $epinetCustomFilters.selectedUserId || null,
410
+ startHour: now.getHours().toString().padStart(2, '0'),
411
+ endHour: now.getHours().toString().padStart(2, '0'),
412
+ });
413
+
414
+ const endDate = new Date(now);
415
+ let startDate = new Date(endDate);
416
+ startDate.setDate(startDate.getDate() - 7);
417
+
418
+ setStartDate(startDate);
419
+ setEndDate(endDate);
420
+ setIsDatePickerOpen(false);
421
+ setHasLocalChanges(false);
422
+ setErrorMessage(null);
423
+ };
424
+
425
+ const radioGroupStyles = `
426
+ .radio-control[data-state="unchecked"] .radio-dot { background-color: #d1d5db; }
427
+ .radio-control[data-state="checked"] .radio-dot { background-color: #0891b2; }
428
+ .radio-control[data-state="checked"] { border-color: #0891b2; }
429
+ .radio-item { border: 1px solid #d1d5db; }
430
+ .radio-item[data-state="checked"] { border-color: #0891b2; }
431
+ .radio-item:hover { background-color: #f3f4f6; }
432
+ @media (max-width: 640px) { .radio-item { flex: 1 1 100%; } }
433
+ @media (min-width: 641px) { .radio-item { flex: 1 1 calc(33.333% - 0.5rem); } }
434
+ `;
435
+
436
+ const getFilterStatusMessage = () => {
437
+ const needsApply = hasLocalChanges;
438
+ const prefix = needsApply ? 'Press Apply Filters to load' : 'Showing';
439
+
440
+ let baseMessage: string;
441
+ if (needsApply && startDate && endDate) {
442
+ baseMessage = `${prefix} from ${formatDateHourDisplay(startDate, localFilters.startHour, false)} to ${formatDateHourDisplay(endDate, localFilters.endHour, true)}`;
443
+ } else if (
444
+ !needsApply &&
445
+ $epinetCustomFilters.startTimeUTC &&
446
+ $epinetCustomFilters.endTimeUTC
447
+ ) {
448
+ baseMessage = `${prefix} ${formatCurrentUTCRange()}`;
449
+ } else {
450
+ baseMessage = `${prefix} from last 7 days`;
451
+ }
452
+
453
+ const userInfo = needsApply
454
+ ? localFilters.selectedUserId
455
+ ? ` for individual user ${localFilters.selectedUserId}`
456
+ : ` for ${
457
+ localFilters.visitorType === 'all'
458
+ ? 'all visitors'
459
+ : localFilters.visitorType === 'anonymous'
460
+ ? 'anonymous visitors'
461
+ : 'known leads'
462
+ }`
463
+ : $epinetCustomFilters.selectedUserId
464
+ ? ` for individual user ${$epinetCustomFilters.selectedUserId}`
465
+ : ` for ${
466
+ $epinetCustomFilters.visitorType === 'all'
467
+ ? 'all visitors'
468
+ : $epinetCustomFilters.visitorType === 'anonymous'
469
+ ? 'anonymous visitors'
470
+ : 'known leads'
471
+ }`;
472
+
473
+ return baseMessage + userInfo;
474
+ };
475
+
476
+ // Get bar styling based on current state
477
+ const getBarStyling = () => {
478
+ if (isApplying) {
479
+ return {
480
+ bgColor: 'bg-blue-50',
481
+ borderColor: 'border-blue-200',
482
+ iconColor: 'text-blue-600',
483
+ textColor: 'text-blue-800',
484
+ icon: (
485
+ <svg className="h-5 w-5 animate-spin" fill="none" viewBox="0 0 24 24">
486
+ <circle
487
+ className="opacity-25"
488
+ cx="12"
489
+ cy="12"
490
+ r="10"
491
+ stroke="currentColor"
492
+ strokeWidth="4"
493
+ />
494
+ <path
495
+ className="opacity-75"
496
+ fill="currentColor"
497
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
498
+ />
499
+ </svg>
500
+ ),
501
+ };
502
+ } else if (errorMessage) {
503
+ return {
504
+ bgColor: 'bg-red-50',
505
+ borderColor: 'border-red-200',
506
+ iconColor: 'text-red-600',
507
+ textColor: 'text-red-800',
508
+ icon: (
509
+ <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
510
+ <path
511
+ fillRule="evenodd"
512
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
513
+ clipRule="evenodd"
514
+ />
515
+ </svg>
516
+ ),
517
+ };
518
+ } else {
519
+ return {
520
+ bgColor: 'bg-amber-50',
521
+ borderColor: 'border-amber-200',
522
+ iconColor: 'text-amber-600',
523
+ textColor: 'text-amber-800',
524
+ icon: (
525
+ <svg className="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
526
+ <path
527
+ fillRule="evenodd"
528
+ d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
529
+ clipRule="evenodd"
530
+ />
531
+ </svg>
532
+ ),
533
+ };
534
+ }
535
+ };
536
+
537
+ const styling = getBarStyling();
538
+
539
+ // Get message based on current state
540
+ const getMessage = () => {
541
+ if (isApplying) {
542
+ return 'Applying filters...';
543
+ } else if (errorMessage) {
544
+ return errorMessage;
545
+ } else {
546
+ return getFilterStatusMessage();
547
+ }
548
+ };
549
+
550
+ return (
551
+ <>
552
+ <div className="space-y-4">
553
+ {$epinetCustomFilters.enabled && (
554
+ <div
555
+ className={`space-y-4 rounded-lg border-2 border-dashed border-gray-200 bg-gray-50 p-4`}
556
+ >
557
+ <div
558
+ className="flex flex-col space-y-4 md:flex-row md:gap-4 md:space-y-0"
559
+ style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}
560
+ >
561
+ <style>{`
562
+ @media (min-width: 768px) {
563
+ .duration-selector-container {
564
+ display: grid;
565
+ grid-template-columns: 200px 1fr;
566
+ gap: 1rem;
567
+ }
568
+ }
569
+ `}</style>
570
+
571
+ <div className="duration-selector-container flex flex-col space-y-4 md:space-y-0">
572
+ {/* Left column: Narrow visitor types stacked */}
573
+ <div className="space-y-2">
574
+ <style>{radioGroupStyles}</style>
575
+ <RadioGroup.Root
576
+ value={localFilters.visitorType}
577
+ onValueChange={({ value }) =>
578
+ updateVisitorType(value as 'all' | 'anonymous' | 'known')
579
+ }
580
+ aria-label="Select visitor type"
581
+ >
582
+ <RadioGroup.Label className="sr-only">
583
+ Visitor Type
584
+ </RadioGroup.Label>
585
+ <div className="flex flex-wrap gap-2 md:flex-col md:gap-2">
586
+ {visitorTypes.map((type) => (
587
+ <RadioGroup.Item
588
+ key={type.id}
589
+ value={type.id}
590
+ className="radio-item relative flex cursor-pointer rounded-lg bg-white px-3 py-2 focus:outline-none"
591
+ >
592
+ <div className="flex w-full items-center justify-between">
593
+ <div className="flex items-center">
594
+ <RadioGroup.ItemControl className="radio-control mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-gray-300">
595
+ <div className="radio-dot h-2 w-2 rounded-full" />
596
+ </RadioGroup.ItemControl>
597
+ <RadioGroup.ItemText>
598
+ <div className="text-sm">
599
+ <p className="font-bold text-gray-800">
600
+ {type.title}
601
+ </p>
602
+ <span className="inline text-gray-600">
603
+ {type.description}
604
+ </span>
605
+ </div>
606
+ </RadioGroup.ItemText>
607
+ </div>
608
+ <div className="hidden shrink-0 text-cyan-600 data-[state=checked]:block">
609
+ <CheckCircleIcon className="h-5 w-5" />
610
+ </div>
611
+ </div>
612
+ <RadioGroup.ItemHiddenInput />
613
+ </RadioGroup.Item>
614
+ ))}
615
+ </div>
616
+ </RadioGroup.Root>
617
+ </div>
618
+
619
+ {/* Right column: Date/time controls and user select in 2 rows */}
620
+ <div className="space-y-4">
621
+ {/* Row 1: Date Range | Start Hour | End Hour */}
622
+ <div className="date-time-row grid grid-cols-1 gap-4">
623
+ <style>{`
624
+ .date-time-row {
625
+ display: grid;
626
+ grid-template-columns: 1fr auto auto;
627
+ gap: 1rem;
628
+ }
629
+
630
+ @media (max-width: 767px) {
631
+ .date-time-grid {
632
+ grid-template-columns: 1fr !important;
633
+ }
634
+ .date-time-row {
635
+ grid-template-columns: 1fr auto;
636
+ grid-template-rows: auto auto;
637
+ }
638
+ .date-time-row > div:nth-child(3) {
639
+ grid-column: 2;
640
+ grid-row: 2;
641
+ }
642
+ }
643
+ `}</style>
644
+ <div className="date-time-grid space-y-1">
645
+ <div className="block text-sm font-bold text-gray-700">
646
+ Date Range (Local Time)
647
+ </div>
648
+ <div className="relative">
649
+ <button
650
+ onClick={() => setIsDatePickerOpen(!isDatePickerOpen)}
651
+ className="w-fit min-w-48 rounded-md border border-gray-300 bg-white px-3 py-2 text-left text-sm shadow-sm"
652
+ aria-label="Toggle date range picker"
653
+ >
654
+ {startDate && endDate
655
+ ? `${formatDateDisplay(startDate)} - ${formatDateDisplay(endDate)}`
656
+ : 'Select date range'}
657
+ </button>
658
+
659
+ {isDatePickerOpen && (
660
+ <div className="absolute z-10 mt-1 w-full rounded-md bg-white p-2 shadow-lg sm:w-auto">
661
+ <div className="mb-2 flex flex-wrap justify-between gap-2">
662
+ <button
663
+ className="rounded-md p-1 text-sm hover:bg-gray-100"
664
+ onClick={() => setPresetDateRange('24h')}
665
+ >
666
+ Last 24 hours
667
+ </button>
668
+ <button
669
+ className="rounded-md p-1 text-sm hover:bg-gray-100"
670
+ onClick={() => setPresetDateRange('7d')}
671
+ >
672
+ Last 7 days
673
+ </button>
674
+ <button
675
+ className="rounded-md p-1 text-sm hover:bg-gray-100"
676
+ onClick={() => setPresetDateRange('28d')}
677
+ >
678
+ Last 28 days
679
+ </button>
680
+ <button
681
+ className="rounded-md p-1 text-sm hover:bg-gray-100"
682
+ onClick={() => setIsDatePickerOpen(false)}
683
+ >
684
+ Close
685
+ </button>
686
+ </div>
687
+
688
+ <div className="mb-2">
689
+ <p className="text-sm font-bold">
690
+ Start date: {formatDateDisplay(startDate)}
691
+ </p>
692
+ <p className="text-sm font-bold">
693
+ End date: {formatDateDisplay(endDate)}
694
+ </p>
695
+ </div>
696
+
697
+ <div className="flex flex-col gap-4 sm:flex-row">
698
+ <div className="flex-1">
699
+ <label
700
+ htmlFor="start-date"
701
+ className="block text-sm font-bold"
702
+ >
703
+ Start
704
+ </label>
705
+ <input
706
+ id="start-date"
707
+ type="date"
708
+ className="w-full rounded border px-2 py-1"
709
+ onChange={(e) =>
710
+ handleDateChange('start', e.target.value)
711
+ }
712
+ value={
713
+ startDate
714
+ ? startDate.toISOString().split('T')[0]
715
+ : ''
716
+ }
717
+ min={minDate.toISOString().split('T')[0]}
718
+ max={maxDate.toISOString().split('T')[0]}
719
+ />
720
+ </div>
721
+ <div className="flex-1">
722
+ <label
723
+ htmlFor="end-date"
724
+ className="block text-sm font-bold"
725
+ >
726
+ End
727
+ </label>
728
+ <input
729
+ id="end-date"
730
+ type="date"
731
+ className="w-full rounded border px-2 py-1"
732
+ onChange={(e) =>
733
+ handleDateChange('end', e.target.value)
734
+ }
735
+ value={
736
+ endDate
737
+ ? endDate.toISOString().split('T')[0]
738
+ : ''
739
+ }
740
+ min={
741
+ startDate
742
+ ? startDate.toISOString().split('T')[0]
743
+ : minDate.toISOString().split('T')[0]
744
+ }
745
+ max={maxDate.toISOString().split('T')[0]}
746
+ />
747
+ </div>
748
+ </div>
749
+ </div>
750
+ )}
751
+ </div>
752
+ </div>
753
+
754
+ <div className="space-y-1" style={{ minWidth: '120px' }}>
755
+ <div className="block text-sm font-bold text-gray-700">
756
+ Start Hour
757
+ </div>
758
+ <select
759
+ id="start-hour"
760
+ value={localFilters.startHour}
761
+ onChange={(e) =>
762
+ updateHour('startHour', e.target.value)
763
+ }
764
+ className="w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-8 text-left text-sm text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-cyan-600"
765
+ >
766
+ {hourOptions.map((option) => (
767
+ <option key={option.value} value={option.value}>
768
+ {option.label}
769
+ </option>
770
+ ))}
771
+ </select>
772
+ </div>
773
+
774
+ <div className="space-y-1" style={{ minWidth: '120px' }}>
775
+ <div className="block text-sm font-bold text-gray-700">
776
+ End Hour
777
+ </div>
778
+ <select
779
+ id="end-hour"
780
+ value={localFilters.endHour}
781
+ onChange={(e) => updateHour('endHour', e.target.value)}
782
+ className="w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-8 text-left text-sm text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-cyan-600"
783
+ >
784
+ {endHourOptions.map((option) => (
785
+ <option key={option.value} value={option.value}>
786
+ {option.label}
787
+ </option>
788
+ ))}
789
+ </select>
790
+ </div>
791
+ </div>
792
+
793
+ {/* Row 2: User select (spans full width) */}
794
+ {paginatedUserCounts.length > 0 && (
795
+ <div
796
+ className="rounded-lg border border-gray-200 bg-white p-3 shadow-sm"
797
+ style={{ maxWidth: '500px' }}
798
+ >
799
+ <div className="mb-2 flex items-center justify-between">
800
+ <h3 className="text-sm font-bold text-gray-700">
801
+ View Individual User Journey
802
+ </h3>
803
+ {totalUserPages > 1 && (
804
+ <div className="flex items-center space-x-2 text-sm">
805
+ <button
806
+ onClick={prevUserPage}
807
+ disabled={currentUserPage === 0}
808
+ className="rounded-full p-1 hover:bg-gray-100 disabled:opacity-50 disabled:hover:bg-transparent"
809
+ aria-label="Previous page"
810
+ >
811
+ <ChevronLeftIcon className="h-5 w-5" />
812
+ </button>
813
+ <span>
814
+ Page {currentUserPage + 1} of {totalUserPages}
815
+ </span>
816
+ <button
817
+ onClick={nextUserPage}
818
+ disabled={currentUserPage >= totalUserPages - 1}
819
+ className="rounded-full p-1 hover:bg-gray-100 disabled:opacity-50 disabled:hover:bg-transparent"
820
+ aria-label="Next page"
821
+ >
822
+ <ChevronRightIcon className="h-5 w-5" />
823
+ </button>
824
+ </div>
825
+ )}
826
+ </div>
827
+
828
+ <div>
829
+ <Select.Root
830
+ collection={createListCollection({
831
+ items: [
832
+ { value: '', label: 'Select user' },
833
+ ...paginatedUserCounts.map((user) => ({
834
+ value: user.id,
835
+ label: `${user.id} (${user.count} events)`,
836
+ })),
837
+ ],
838
+ })}
839
+ value={
840
+ localFilters.selectedUserId
841
+ ? [localFilters.selectedUserId]
842
+ : ['']
843
+ }
844
+ onValueChange={({ value }) =>
845
+ updateSelectedUser(value[0] || null)
846
+ }
847
+ >
848
+ <Select.Label className="sr-only">
849
+ Select user
850
+ </Select.Label>
851
+ <Select.Control>
852
+ <Select.Trigger
853
+ className="relative w-fit min-w-64 cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-sm text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-cyan-600"
854
+ aria-label="Select individual user"
855
+ >
856
+ <Select.ValueText placeholder="Select user">
857
+ {localFilters.selectedUserId
858
+ ? localFilters.selectedUserId
859
+ : 'Select user'}
860
+ </Select.ValueText>
861
+ <Select.Indicator className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
862
+ <span className="h-5 w-5 text-gray-500">▼</span>
863
+ </Select.Indicator>
864
+ </Select.Trigger>
865
+ </Select.Control>
866
+ <Portal>
867
+ <Select.Positioner>
868
+ <Select.Content className="z-10 mt-2 max-h-96 w-[var(--trigger-width)] overflow-auto rounded-md bg-white text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
869
+ {paginatedUserCounts.length > 0 ? (
870
+ [
871
+ <Select.Item
872
+ key="empty"
873
+ item={{ value: '', label: 'Select user' }}
874
+ 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"
875
+ >
876
+ <Select.ItemText>
877
+ Select user
878
+ </Select.ItemText>
879
+ </Select.Item>,
880
+ ...paginatedUserCounts.map((user) => (
881
+ <Select.Item
882
+ key={user.id}
883
+ item={{
884
+ value: user.id,
885
+ label: `${user.id} (${user.count} events)`,
886
+ }}
887
+ 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"
888
+ >
889
+ <Select.ItemText>
890
+ {user.id}{' '}
891
+ <span className="text-xs text-gray-500">
892
+ ({user.count} events)
893
+ </span>
894
+ </Select.ItemText>
895
+ </Select.Item>
896
+ )),
897
+ ]
898
+ ) : (
899
+ <div className="p-2 text-sm text-gray-500">
900
+ No users available
901
+ </div>
902
+ )}
903
+ </Select.Content>
904
+ </Select.Positioner>
905
+ </Portal>
906
+ </Select.Root>
907
+ </div>
908
+ </div>
909
+ )}
910
+ </div>
911
+ </div>
912
+ </div>
913
+
914
+ {/* Current status display - only show when no changes pending */}
915
+ {!hasLocalChanges && $epinetCustomFilters.enabled && (
916
+ <div className="flex items-center justify-between rounded-md bg-gray-100 p-2">
917
+ <p className="text-sm font-bold text-gray-700">
918
+ {getFilterStatusMessage()}
919
+ </p>
920
+ <button
921
+ onClick={() => setShowTable(!showTable)}
922
+ className="rounded-md bg-cyan-600 px-3 py-1 text-sm text-white shadow-sm transition-colors duration-200 hover:bg-cyan-700"
923
+ >
924
+ {showTable ? 'Hide' : 'Show'} Table View
925
+ </button>
926
+ </div>
927
+ )}
928
+
929
+ {showTable && (
930
+ <EpinetTableView
931
+ fullContentMap={fullContentMap || []}
932
+ isLoading={isLoading}
933
+ />
934
+ )}
935
+ </div>
936
+ )}
937
+ </div>
938
+
939
+ {/* UnsavedChangesBar-style sticky bottom bar */}
940
+ {isVisible && (
941
+ <div
942
+ className={`fixed bottom-0 left-0 right-0 z-50 transform pr-12 transition-all duration-300 ease-in-out ${
943
+ isAnimating
944
+ ? 'translate-y-0 opacity-100'
945
+ : 'translate-y-full opacity-0'
946
+ }`}
947
+ >
948
+ {/* Backdrop blur overlay */}
949
+ <div className="absolute inset-0 bg-black/10 backdrop-blur-sm" />
950
+
951
+ {/* Main content bar */}
952
+ <div className="relative mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
953
+ <div
954
+ className={`flex items-center justify-between rounded-lg border px-6 py-4 shadow-lg ${styling.bgColor} ${styling.borderColor}`}
955
+ >
956
+ {/* Icon + message */}
957
+ <div className="flex items-center space-x-3">
958
+ <div className={`flex-shrink-0 ${styling.iconColor}`}>
959
+ {styling.icon}
960
+ </div>
961
+ <div>
962
+ <p className={`text-sm font-bold ${styling.textColor}`}>
963
+ {getMessage()}
964
+ </p>
965
+ {errorMessage && !isApplying && (
966
+ <p className="mt-1 text-xs text-red-600">
967
+ Click Apply Filters to try again
968
+ </p>
969
+ )}
970
+ </div>
971
+ </div>
972
+
973
+ {/* Action buttons */}
974
+ <div className="flex items-center space-x-3">
975
+ {/* Cancel button - only show when not applying */}
976
+ {!isApplying && (
977
+ <button
978
+ type="button"
979
+ onClick={cancelChanges}
980
+ className={`rounded-md border px-3 py-2 text-sm font-bold shadow-sm transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 ${
981
+ errorMessage
982
+ ? 'border-red-300 bg-white text-red-800 hover:bg-red-50 focus:ring-red-500'
983
+ : 'border-amber-300 bg-white text-amber-800 hover:bg-amber-50 focus:ring-amber-500'
984
+ }`}
985
+ >
986
+ Cancel
987
+ </button>
988
+ )}
989
+
990
+ {/* Apply Filters button - only show when changes exist and not already applied */}
991
+ {hasLocalChanges && (
992
+ <button
993
+ type="button"
994
+ onClick={updateDateRange}
995
+ disabled={!startDate || !endDate || isApplying}
996
+ className={`rounded-md border border-transparent px-4 py-2 text-sm font-bold text-white shadow-sm transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed ${
997
+ isApplying
998
+ ? 'bg-blue-500 opacity-75'
999
+ : errorMessage
1000
+ ? startDate && endDate
1001
+ ? 'bg-red-600 hover:bg-red-700 focus:ring-red-500'
1002
+ : 'bg-red-400 opacity-50'
1003
+ : startDate && endDate
1004
+ ? 'bg-amber-600 hover:bg-amber-700 focus:ring-amber-500'
1005
+ : 'bg-amber-400 opacity-50'
1006
+ }`}
1007
+ >
1008
+ {isApplying ? 'Applying...' : 'Apply Filters'}
1009
+ </button>
1010
+ )}
1011
+ </div>
1012
+ </div>
1013
+ </div>
1014
+ </div>
1015
+ )}
1016
+ </>
1017
+ );
1018
+ };
1019
+
1020
+ export default EpinetDurationSelector;