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,424 @@
1
+ import {
2
+ useEffect,
3
+ useState,
4
+ useCallback,
5
+ useMemo,
6
+ type ReactNode,
7
+ } from 'react';
8
+ import { useStore } from '@nanostores/react';
9
+ import { epinetCustomFilters } from '@/stores/analytics';
10
+ import { TractStackAPI } from '@/utils/api';
11
+ import SankeyDiagram from './SankeyDiagram';
12
+ import EpinetDurationSelector from './EpinetDurationSelector';
13
+ import type { FullContentMapItem } from '@/types/tractstack';
14
+
15
+ interface ErrorBoundaryProps {
16
+ children: ReactNode;
17
+ fallback: ReactNode;
18
+ }
19
+
20
+ const ErrorBoundary = ({ children, fallback }: ErrorBoundaryProps) => {
21
+ const [hasError, setHasError] = useState(false);
22
+
23
+ const handleError = useCallback(() => {
24
+ setHasError(true);
25
+ }, []);
26
+
27
+ if (hasError) return <>{fallback}</>;
28
+
29
+ return <div onError={handleError}>{children}</div>;
30
+ };
31
+
32
+ const EpinetWrapper = ({
33
+ fullContentMap,
34
+ }: {
35
+ fullContentMap: FullContentMapItem[];
36
+ }) => {
37
+ // Use the global store instead of local state
38
+ const $epinetCustomFilters = useStore(epinetCustomFilters);
39
+
40
+ const [analytics, setAnalytics] = useState<{
41
+ epinet: any;
42
+ isLoading: boolean;
43
+ status: string;
44
+ error: string | null;
45
+ }>({
46
+ epinet: null,
47
+ isLoading: false,
48
+ status: 'idle',
49
+ error: null,
50
+ });
51
+
52
+ const [pollingTimer, setPollingTimer] = useState<NodeJS.Timeout | null>(null);
53
+ const [pollingAttempts, setPollingAttempts] = useState(0);
54
+ const [epinetId, setEpinetId] = useState<string | null>(null);
55
+
56
+ const MAX_POLLING_ATTEMPTS = 3;
57
+ const POLLING_DELAYS = [2000, 5000, 10000]; // 2s, 5s, 10s
58
+
59
+ // Initialize TractStackAPI
60
+ const api = useMemo(
61
+ () => new TractStackAPI(window.TRACTSTACK_CONFIG?.tenantId || 'default'),
62
+ []
63
+ );
64
+
65
+ // Clear polling timer on unmount
66
+ useEffect(() => {
67
+ return () => {
68
+ if (pollingTimer) {
69
+ clearTimeout(pollingTimer);
70
+ }
71
+ };
72
+ }, [pollingTimer]);
73
+
74
+ useEffect(() => {
75
+ const discoverEpinetId = async () => {
76
+ try {
77
+ // First, try to find a promoted epinet from content map
78
+ const promotedEpinet = fullContentMap.find(
79
+ (item) => item.type === 'Epinet' && item.promoted
80
+ );
81
+
82
+ if (promotedEpinet) {
83
+ setEpinetId(promotedEpinet.id);
84
+ return;
85
+ }
86
+
87
+ // If no promoted epinet, get first epinet from content map
88
+ const firstEpinet = fullContentMap.find(
89
+ (item) => item.type === 'Epinet'
90
+ );
91
+
92
+ if (firstEpinet) {
93
+ setEpinetId(firstEpinet.id);
94
+ return;
95
+ }
96
+
97
+ // Fallback: no epinet found
98
+ console.warn('No epinet found in content map');
99
+ setEpinetId(null);
100
+ } catch (error) {
101
+ console.error('Error discovering epinet ID:', error);
102
+ setEpinetId(null);
103
+ }
104
+ };
105
+
106
+ discoverEpinetId();
107
+ }, [fullContentMap]);
108
+
109
+ // Initialize epinet custom filters with default values on mount
110
+ useEffect(() => {
111
+ const nowUTC = new Date();
112
+ const oneWeekAgoUTC = new Date(nowUTC.getTime() - 7 * 24 * 60 * 60 * 1000);
113
+
114
+ epinetCustomFilters.set(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
115
+ enabled: true,
116
+ visitorType: 'all',
117
+ selectedUserId: null,
118
+ startTimeUTC: oneWeekAgoUTC.toISOString(),
119
+ endTimeUTC: nowUTC.toISOString(),
120
+ userCounts: [],
121
+ hourlyNodeActivity: {},
122
+ });
123
+ }, []);
124
+
125
+ // Detect current duration type from epinetCustomFilters (for UI helpers only)
126
+ //const currentDurationHelper = useMemo(():
127
+ // | 'daily'
128
+ // | 'weekly'
129
+ // | 'monthly'
130
+ // | 'custom' => {
131
+ // const { startTimeUTC, endTimeUTC } = $epinetCustomFilters;
132
+
133
+ // if (startTimeUTC && endTimeUTC) {
134
+ // const startTime = new Date(startTimeUTC);
135
+ // const endTime = new Date(endTimeUTC);
136
+ // const diffMs = endTime.getTime() - startTime.getTime();
137
+ // const diffHours = diffMs / (1000 * 60 * 60);
138
+
139
+ // if (Math.abs(diffHours - 24) <= 1) return 'daily';
140
+ // if (Math.abs(diffHours - 168) <= 1) return 'weekly';
141
+ // if (Math.abs(diffHours - 672) <= 1) return 'monthly';
142
+ // return 'custom';
143
+ // }
144
+
145
+ // return 'weekly'; // default
146
+ //}, [$epinetCustomFilters.startTimeUTC, $epinetCustomFilters.endTimeUTC]);
147
+
148
+ // Fetch data when epinet ID is available
149
+ useEffect(() => {
150
+ if (epinetId) {
151
+ fetchEpinetData();
152
+ }
153
+ }, [epinetId]);
154
+
155
+ // Watch for changes in the global filters and refetch data
156
+ useEffect(() => {
157
+ if (
158
+ epinetId &&
159
+ $epinetCustomFilters.enabled &&
160
+ $epinetCustomFilters.visitorType !== null &&
161
+ $epinetCustomFilters.startTimeUTC !== null &&
162
+ $epinetCustomFilters.endTimeUTC !== null
163
+ ) {
164
+ setPollingAttempts(0);
165
+ fetchEpinetData();
166
+ }
167
+ }, [
168
+ epinetId,
169
+ $epinetCustomFilters.enabled,
170
+ $epinetCustomFilters.visitorType,
171
+ $epinetCustomFilters.selectedUserId,
172
+ $epinetCustomFilters.startTimeUTC,
173
+ $epinetCustomFilters.endTimeUTC,
174
+ ]);
175
+
176
+ // Handle filter preset changes
177
+ //const handleFilterChange = useCallback(
178
+ // (newValue: string) => {
179
+ // const nowUTC = new Date();
180
+ // const hoursBack: number =
181
+ // newValue === 'daily' ? 24 : newValue === 'weekly' ? 168 : 672;
182
+ // const startTimeUTC = new Date(
183
+ // nowUTC.getTime() - hoursBack * 60 * 60 * 1000
184
+ // );
185
+
186
+ // epinetCustomFilters.set(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
187
+ // ...$epinetCustomFilters,
188
+ // enabled: true,
189
+ // startTimeUTC: startTimeUTC.toISOString(),
190
+ // endTimeUTC: nowUTC.toISOString(),
191
+ // });
192
+ // },
193
+ // [$epinetCustomFilters]
194
+ //);
195
+
196
+ const fetchEpinetData = useCallback(async () => {
197
+ if (!epinetId) return;
198
+
199
+ try {
200
+ setAnalytics((prev) => ({ ...prev, isLoading: true }));
201
+
202
+ if (pollingTimer) {
203
+ clearTimeout(pollingTimer);
204
+ setPollingTimer(null);
205
+ }
206
+
207
+ // Build query parameters
208
+ const params = new URLSearchParams();
209
+
210
+ // Convert UTC timestamps to hours-back integers (what backend expects)
211
+ if (
212
+ $epinetCustomFilters.startTimeUTC &&
213
+ $epinetCustomFilters.endTimeUTC
214
+ ) {
215
+ const now = new Date();
216
+ const startTime = new Date($epinetCustomFilters.startTimeUTC);
217
+ const endTime = new Date($epinetCustomFilters.endTimeUTC);
218
+
219
+ const startHour = Math.ceil(
220
+ (now.getTime() - startTime.getTime()) / (1000 * 60 * 60)
221
+ );
222
+ const endHour = Math.floor(
223
+ (now.getTime() - endTime.getTime()) / (1000 * 60 * 60)
224
+ );
225
+
226
+ params.append('startHour', startHour.toString());
227
+ params.append('endHour', endHour.toString());
228
+ }
229
+
230
+ params.append('visitorType', $epinetCustomFilters.visitorType || 'all');
231
+ if ($epinetCustomFilters.selectedUserId) {
232
+ params.append('userId', $epinetCustomFilters.selectedUserId);
233
+ }
234
+
235
+ // Use TractStackAPI instead of raw fetch
236
+ const response = await api.get(
237
+ `/api/v1/analytics/epinet/${epinetId}?${params.toString()}`
238
+ );
239
+
240
+ if (!response.success) {
241
+ throw new Error(`API request failed: ${response.error}`);
242
+ }
243
+
244
+ const result = response.data;
245
+
246
+ if (result.success !== false) {
247
+ // Check if data is still loading
248
+ const epinetData = result.epinet;
249
+
250
+ if (
251
+ epinetData &&
252
+ (epinetData.status === 'loading' ||
253
+ epinetData.status === 'refreshing')
254
+ ) {
255
+ // If data is still loading, poll again after delay
256
+ if (pollingAttempts < MAX_POLLING_ATTEMPTS) {
257
+ const delayMs =
258
+ POLLING_DELAYS[pollingAttempts] ||
259
+ POLLING_DELAYS[POLLING_DELAYS.length - 1];
260
+
261
+ const newTimer = setTimeout(() => {
262
+ setPollingAttempts(pollingAttempts + 1);
263
+ fetchEpinetData();
264
+ }, delayMs);
265
+
266
+ setPollingTimer(newTimer);
267
+ return;
268
+ }
269
+ }
270
+
271
+ setAnalytics((prev) => ({
272
+ ...prev,
273
+ epinet: result.epinet,
274
+ status: 'complete',
275
+ error: null,
276
+ }));
277
+
278
+ // Update the global store with additional data from API response
279
+ epinetCustomFilters.set(
280
+ window.TRACTSTACK_CONFIG?.tenantId || 'default',
281
+ {
282
+ ...$epinetCustomFilters,
283
+ userCounts: result.userCounts || [],
284
+ hourlyNodeActivity: result.hourlyNodeActivity || {},
285
+ }
286
+ );
287
+
288
+ setPollingAttempts(0);
289
+ } else {
290
+ throw new Error(result.error || 'Unknown API error');
291
+ }
292
+ } catch (error) {
293
+ setAnalytics((prev) => ({
294
+ ...prev,
295
+ error: error instanceof Error ? error.message : 'Unknown error',
296
+ status: 'error',
297
+ }));
298
+
299
+ // Schedule a retry if we haven't reached max attempts
300
+ if (pollingAttempts < MAX_POLLING_ATTEMPTS) {
301
+ const delayMs =
302
+ POLLING_DELAYS[pollingAttempts] ||
303
+ POLLING_DELAYS[POLLING_DELAYS.length - 1];
304
+
305
+ const newTimer = setTimeout(() => {
306
+ setPollingAttempts(pollingAttempts + 1);
307
+ fetchEpinetData();
308
+ }, delayMs);
309
+
310
+ setPollingTimer(newTimer);
311
+ }
312
+ } finally {
313
+ setAnalytics((prev) => ({ ...prev, isLoading: false }));
314
+ }
315
+ }, [epinetId, $epinetCustomFilters, pollingAttempts, api]);
316
+
317
+ const { epinet, isLoading, status, error } = analytics;
318
+
319
+ // Show loading while discovering epinet ID
320
+ if (!epinetId) {
321
+ return (
322
+ <div className="flex h-96 w-full items-center justify-center rounded bg-gray-100">
323
+ <div className="text-center">
324
+ <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>
325
+ <p className="mt-4 text-sm text-gray-600">
326
+ Discovering analytics configuration...
327
+ </p>
328
+ </div>
329
+ </div>
330
+ );
331
+ }
332
+
333
+ if ((isLoading || status === 'loading') && !epinet) {
334
+ return (
335
+ <div className="flex h-96 w-full items-center justify-center rounded bg-gray-100">
336
+ <div className="text-center">
337
+ <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>
338
+ <p className="mt-4 text-sm text-gray-600">
339
+ Computing user journey data...
340
+ </p>
341
+ </div>
342
+ </div>
343
+ );
344
+ }
345
+
346
+ if (error && !epinet) {
347
+ return (
348
+ <div className="rounded-lg bg-red-50 p-4 text-red-800">
349
+ <p className="font-bold">Error loading user journey visualization</p>
350
+ <p className="text-sm">{error}</p>
351
+ <button
352
+ onClick={() => {
353
+ setPollingAttempts(0);
354
+ fetchEpinetData();
355
+ }}
356
+ className="mt-3 rounded bg-red-600 px-3 py-1 text-sm text-white hover:bg-red-700"
357
+ >
358
+ Retry
359
+ </button>
360
+ </div>
361
+ );
362
+ }
363
+
364
+ if (
365
+ !epinet ||
366
+ !epinet.nodes ||
367
+ !epinet.links ||
368
+ epinet.nodes.length === 0 ||
369
+ epinet.links.length === 0
370
+ ) {
371
+ return (
372
+ <div className="rounded-lg bg-gray-50 p-8 text-center text-gray-800">
373
+ <p>
374
+ No user journey data is available yet. This visualization will appear
375
+ when users start interacting with your content.
376
+ </p>
377
+ </div>
378
+ );
379
+ }
380
+
381
+ return (
382
+ <div className="px-3.5 py-12 md:px-6">
383
+ <ErrorBoundary
384
+ fallback={
385
+ <div className="rounded-lg bg-red-50 p-4 text-red-800">
386
+ <p className="font-bold">
387
+ Error rendering user journey visualization
388
+ </p>
389
+ <button
390
+ onClick={() => window.location.reload()}
391
+ className="mt-3 rounded bg-red-600 px-3 py-1 text-sm text-white hover:bg-red-700"
392
+ >
393
+ Reload Page
394
+ </button>
395
+ </div>
396
+ }
397
+ >
398
+ <div className="space-y-6">
399
+ <div className="rounded-lg bg-white p-6 shadow">
400
+ <div className="mb-4 flex items-center justify-between">
401
+ {(isLoading || status === 'loading') && (
402
+ <div className="flex items-center space-x-2 text-sm text-gray-500">
403
+ <div className="h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-gray-600"></div>
404
+ <span>Updating...</span>
405
+ </div>
406
+ )}
407
+ </div>
408
+ <SankeyDiagram
409
+ data={epinet}
410
+ isLoading={isLoading || status === 'loading'}
411
+ />
412
+ </div>
413
+
414
+ <EpinetDurationSelector
415
+ fullContentMap={fullContentMap}
416
+ isLoading={isLoading || status === 'loading'}
417
+ />
418
+ </div>
419
+ </ErrorBoundary>
420
+ </div>
421
+ );
422
+ };
423
+
424
+ export default EpinetWrapper;
@@ -0,0 +1,273 @@
1
+ ---
2
+ import type { FullContentMapItem } from '@/types/tractstack';
3
+
4
+ export interface Props {
5
+ options?: {
6
+ params?: {
7
+ options?: string;
8
+ };
9
+ };
10
+ contentMap: FullContentMapItem[];
11
+ }
12
+
13
+ const { options, contentMap } = Astro.props;
14
+
15
+ // Parse component options
16
+ let parsedOptions;
17
+ try {
18
+ parsedOptions = JSON.parse(options?.params?.options || '{}');
19
+ } catch (e) {
20
+ console.error('Invalid options', e);
21
+ parsedOptions = {
22
+ defaultMode: 'ordered',
23
+ featuredId: '',
24
+ storyfragmentIds: '',
25
+ };
26
+ }
27
+
28
+ const defaultMode = parsedOptions.defaultMode || 'ordered';
29
+ const featuredId = parsedOptions.featuredId || '';
30
+ const storyfragmentIdsArray = parsedOptions.storyfragmentIds
31
+ ? parsedOptions.storyfragmentIds.split(',')
32
+ : [];
33
+
34
+ // Find the featured story from the contentMap
35
+ const featuredStory = contentMap.find(
36
+ (item: FullContentMapItem) =>
37
+ item.id === featuredId && item.type === 'StoryFragment'
38
+ );
39
+ // Filter and sort the included stories, excluding the featured story
40
+ let includedStories = contentMap.filter(
41
+ (item: FullContentMapItem) =>
42
+ storyfragmentIdsArray.includes(item.id) &&
43
+ item.type === 'StoryFragment' &&
44
+ item.id !== featuredId
45
+ );
46
+
47
+ // Sort included stories based on defaultMode ('ordered' or 'recent')
48
+ // The 'popular' mode will be handled on the client-side
49
+ if (defaultMode === 'ordered') {
50
+ includedStories.sort(
51
+ (a: FullContentMapItem, b: FullContentMapItem) =>
52
+ storyfragmentIdsArray.indexOf(a.id) - storyfragmentIdsArray.indexOf(b.id)
53
+ );
54
+ } else if (defaultMode === 'recent') {
55
+ includedStories.sort((a: FullContentMapItem, b: FullContentMapItem) => {
56
+ const dateA = a.changed ? new Date(a.changed).getTime() : 0;
57
+ const dateB = b.changed ? new Date(b.changed).getTime() : 0;
58
+ return dateB - dateA;
59
+ });
60
+ }
61
+
62
+ // Limit to 5 stories for display
63
+ const displayedStories = includedStories.slice(0, 5);
64
+
65
+ // Function to format dates
66
+ function formatDate(dateString: string | null): string {
67
+ if (!dateString) return 'Unknown';
68
+ const date = new Date(dateString);
69
+ return new Intl.DateTimeFormat('en-US', {
70
+ year: 'numeric',
71
+ month: 'long',
72
+ day: 'numeric',
73
+ }).format(date);
74
+ }
75
+ ---
76
+
77
+ <div class="mx-auto flex max-w-7xl flex-col gap-4 py-12 md:flex-row">
78
+ <div class="p-4 md:w-3/5">
79
+ {
80
+ featuredStory ? (
81
+ <a href={`/${featuredStory.slug}`} class="group block">
82
+ <div class="space-y-6 p-2 group-hover:bg-slate-50">
83
+ {featuredStory.thumbSrc && (
84
+ <img
85
+ src={featuredStory.thumbSrc}
86
+ alt={featuredStory.title}
87
+ class="h-auto w-full rounded-lg object-cover"
88
+ />
89
+ )}
90
+ <h2 class="text-2xl font-bold text-black transition-colors group-hover:text-gray-900">
91
+ {featuredStory.title}
92
+ </h2>
93
+ {featuredStory.description && (
94
+ <p class="text-base text-gray-800">{featuredStory.description}</p>
95
+ )}
96
+ <p class="text-sm text-gray-600">
97
+ {featuredStory.changed && formatDate(featuredStory.changed)}
98
+ </p>
99
+ </div>
100
+ </a>
101
+ ) : (
102
+ <p class="italic text-cyan-600">No featured story selected.</p>
103
+ )
104
+ }
105
+ </div>
106
+
107
+ <div
108
+ class="border-t-2 border-slate-100 p-4 md:w-2/5 md:border-l-2 md:border-t-0"
109
+ >
110
+ {
111
+ displayedStories.length > 0 ? (
112
+ <div class="space-y-4">
113
+ {displayedStories.map((story: FullContentMapItem) => (
114
+ <a href={`/${story.slug}`} class="group block">
115
+ <div class="flex items-start space-x-4 p-1 group-hover:bg-slate-50">
116
+ {story.thumbSrc && (
117
+ <img
118
+ src={story.thumbSrc}
119
+ alt={story.title}
120
+ style="width: 100px; height: auto;"
121
+ class="rounded-md"
122
+ />
123
+ )}
124
+ <div class="flex-1">
125
+ <h3 class="text-lg font-bold text-black transition-colors group-hover:text-gray-900">
126
+ {story.title}
127
+ </h3>
128
+ {story.description && (
129
+ <p class="line-clamp-2 text-sm text-gray-800">
130
+ {story.description}
131
+ </p>
132
+ )}
133
+ <p class="mt-1 text-xs text-gray-600">
134
+ {story.changed && formatDate(story.changed)}
135
+ </p>
136
+ </div>
137
+ </div>
138
+ </a>
139
+ ))}
140
+ </div>
141
+ ) : (
142
+ <p class="italic text-cyan-600">Check back soon for more stories.</p>
143
+ )
144
+ }
145
+ </div>
146
+ </div>
147
+
148
+ <script
149
+ is:inline
150
+ define:vars={{
151
+ includedStories,
152
+ defaultMode,
153
+ }}
154
+ >
155
+ // Client-side script to fetch and apply "hot content" analytics
156
+ let hotContent = [];
157
+ let hasHotContent = false;
158
+ let isLoading = false;
159
+ let retryCount = 0;
160
+ const maxRetries = 2;
161
+
162
+ // Fetches hot content data from the analytics endpoint with polling
163
+ async function fetchHotContent() {
164
+ if (isLoading) return;
165
+ isLoading = true;
166
+
167
+ try {
168
+ const goBackend = window.location.protocol + '//' + window.location.host;
169
+ const tenantId = window.TRACTSTACK_CONFIG?.tenantId || 'default';
170
+ const response = await fetch(
171
+ `${goBackend}/api/v1/analytics/content-summary`,
172
+ {
173
+ method: 'GET',
174
+ headers: {
175
+ 'Content-Type': 'application/json',
176
+ 'X-Tenant-ID': tenantId,
177
+ },
178
+ }
179
+ );
180
+
181
+ if (!response.ok) {
182
+ throw new Error(`HTTP error! status: ${response.status}`);
183
+ }
184
+
185
+ const data = await response.json();
186
+
187
+ // If data is fetched, update state and re-order content
188
+ if (data.hotContent && data.hotContent.length > 0) {
189
+ hotContent = data.hotContent;
190
+ hasHotContent = true;
191
+ updateContentOrder();
192
+ } else if (retryCount < maxRetries) {
193
+ retryCount++;
194
+ const delayMs = retryCount === 1 ? 3000 : 6000;
195
+ setTimeout(fetchHotContent, delayMs);
196
+ }
197
+ } catch (error) {
198
+ console.warn('Could not fetch hot content:', error);
199
+ if (retryCount < maxRetries) {
200
+ retryCount++;
201
+ const delayMs = retryCount === 1 ? 3000 : 6000;
202
+ setTimeout(fetchHotContent, delayMs);
203
+ }
204
+ } finally {
205
+ isLoading = false;
206
+ }
207
+ }
208
+
209
+ // Re-orders stories based on popularity if mode is 'popular'
210
+ function updateContentOrder() {
211
+ if (hasHotContent && defaultMode === 'popular') {
212
+ const viewsMap = new Map(
213
+ hotContent.map((item) => [item.id, item.totalEvents])
214
+ );
215
+
216
+ const sortedByPopular = [...includedStories].sort((a, b) => {
217
+ const aViews = viewsMap.get(a.id) || 0;
218
+ const bViews = viewsMap.get(b.id) || 0;
219
+ // Fallback to date sort if views are equal
220
+ if (bViews === aViews) {
221
+ const dateA = a.changed ? new Date(a.changed).getTime() : 0;
222
+ const dateB = b.changed ? new Date(b.changed).getTime() : 0;
223
+ return dateB - dateA;
224
+ }
225
+ return bViews - aViews;
226
+ });
227
+
228
+ updateDisplayedStories(sortedByPopular.slice(0, 5));
229
+ }
230
+ }
231
+
232
+ // Updates the DOM with the new list of stories
233
+ function updateDisplayedStories(stories) {
234
+ const rightColumn = document.querySelector('.md\\:w-2\\/5 .space-y-4');
235
+ if (!rightColumn) return;
236
+
237
+ rightColumn.innerHTML = stories
238
+ .map((story) => {
239
+ const formattedDate = story.changed
240
+ ? new Intl.DateTimeFormat('en-US', {
241
+ year: 'numeric',
242
+ month: 'long',
243
+ day: 'numeric',
244
+ }).format(new Date(story.changed))
245
+ : 'Unknown';
246
+ const imageHtml = story.thumbSrc
247
+ ? `<img src="${story.thumbSrc}" alt="${story.title}" style="width: 100px; height: auto;" class="rounded-md">`
248
+ : '';
249
+ const descriptionHtml = story.description
250
+ ? `<p class="line-clamp-2 text-sm text-gray-800">${story.description}</p>`
251
+ : '';
252
+
253
+ return `
254
+ <a href="/${story.slug}" class="group block">
255
+ <div class="flex items-start space-x-4 p-1 group-hover:bg-slate-50">
256
+ ${imageHtml}
257
+ <div class="flex-1">
258
+ <h3 class="text-lg font-bold text-black transition-colors group-hover:text-gray-900">
259
+ ${story.title}
260
+ </h3>
261
+ ${descriptionHtml}
262
+ <p class="mt-1 text-xs text-gray-600">${formattedDate}</p>
263
+ </div>
264
+ </div>
265
+ </a>
266
+ `;
267
+ })
268
+ .join('');
269
+ }
270
+
271
+ // Start fetching analytics on page load
272
+ document.addEventListener('DOMContentLoaded', fetchHotContent);
273
+ </script>