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,649 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { useStore } from '@nanostores/react';
3
+ import { fullContentMapStore } from '@/stores/storykeep';
4
+ import { classNames, cloneDeep } from '@/utils/helpers';
5
+ import { getCtx } from '@/stores/nodes';
6
+ import ColorPickerCombo from '@/components/fields/ColorPickerCombo';
7
+ import type { BrandConfig } from '@/types/tractstack';
8
+ import type { PaneNode } from '@/types/compositorTypes';
9
+
10
+ const PER_PAGE = 20;
11
+
12
+ // V2 Analytics Data Structure
13
+ interface StoryfragmentAnalytics {
14
+ id: string;
15
+ total_actions: number;
16
+ unique_visitors: number;
17
+ last_24h_actions: number;
18
+ last_7d_actions: number;
19
+ last_28d_actions: number;
20
+ last_24h_unique_visitors: number;
21
+ last_7d_unique_visitors: number;
22
+ last_28d_unique_visitors: number;
23
+ total_leads: number;
24
+ }
25
+
26
+ interface ListContentSetupProps {
27
+ params?: Record<string, string>;
28
+ nodeId: string;
29
+ config?: BrandConfig;
30
+ }
31
+
32
+ const ListContentSetup = ({
33
+ params,
34
+ nodeId,
35
+ config,
36
+ }: ListContentSetupProps) => {
37
+ const [analyticsData, setAnalyticsData] = useState<
38
+ Record<string, StoryfragmentAnalytics>
39
+ >({});
40
+ const [isAnalyticsLoading, setIsAnalyticsLoading] = useState(true);
41
+ const $contentMap = useStore(fullContentMapStore);
42
+
43
+ const [isPanelOpen, setIsPanelOpen] = useState(false);
44
+
45
+ const [selectedMode, setSelectedMode] = useState(
46
+ params?.defaultMode || 'recent'
47
+ );
48
+ const [excludedIds, setExcludedIds] = useState<string[]>(
49
+ params?.excludedIds ? params.excludedIds.split(',') : []
50
+ );
51
+ const [selectedTopics, setSelectedTopics] = useState<string[]>(
52
+ params?.topics ? params.topics.split(',') : []
53
+ );
54
+ const [pageSize, setPageSize] = useState(
55
+ params?.pageSize ? parseInt(params.pageSize) : 10
56
+ );
57
+ const [currentPage, setCurrentPage] = useState(1);
58
+ const [bgColor, setBgColor] = useState(params?.bgColor || '');
59
+
60
+ const isInitialMount = useRef(true);
61
+
62
+ const ctx = getCtx();
63
+
64
+ const hasConfiguration =
65
+ selectedTopics.length > 0 ||
66
+ excludedIds.length > 0 ||
67
+ pageSize !== 10 ||
68
+ bgColor !== '';
69
+
70
+ const validPages = $contentMap.filter(
71
+ (item) =>
72
+ item.type === 'StoryFragment' &&
73
+ typeof item.description === 'string' &&
74
+ typeof item.thumbSrc === 'string' &&
75
+ typeof item.thumbSrcSet === 'string' &&
76
+ typeof item.changed === 'string'
77
+ );
78
+
79
+ // Build topic map for filtering
80
+ const topicMap = new Map<string, { count: number; pageIds: string[] }>();
81
+ validPages.forEach((page) => {
82
+ if (page.topics && page.topics.length > 0) {
83
+ page.topics.forEach((topic) => {
84
+ const topicData = topicMap.get(topic) || { count: 0, pageIds: [] };
85
+ topicData.count += 1;
86
+ topicData.pageIds.push(page.id);
87
+ topicMap.set(topic, topicData);
88
+ });
89
+ }
90
+ });
91
+
92
+ const fetchAnalyticsData = async () => {
93
+ try {
94
+ setIsAnalyticsLoading(true);
95
+ // Updated to use V2 API endpoint
96
+ const response = await fetch('/api/v1/analytics/storyfragments', {
97
+ headers: {
98
+ 'X-Tenant-ID': window.TRACTSTACK_CONFIG?.tenantId || 'default',
99
+ },
100
+ });
101
+
102
+ if (!response.ok) {
103
+ throw new Error(`HTTP error! status: ${response.status}`);
104
+ }
105
+
106
+ const analyticsArray = await response.json();
107
+
108
+ // Transform array to a map keyed by ID for easier lookup
109
+ // V2 API returns array directly, not wrapped in a success/data structure
110
+ const analyticsById = Array.isArray(analyticsArray)
111
+ ? analyticsArray.reduce(
112
+ (
113
+ acc: Record<string, StoryfragmentAnalytics>,
114
+ item: StoryfragmentAnalytics
115
+ ) => {
116
+ acc[item.id] = item;
117
+ return acc;
118
+ },
119
+ {}
120
+ )
121
+ : {};
122
+
123
+ setAnalyticsData(analyticsById);
124
+ } catch (error) {
125
+ console.error('Error fetching analytics data:', error);
126
+ // Set empty analytics on error to prevent blocking the UI
127
+ setAnalyticsData({});
128
+ } finally {
129
+ setIsAnalyticsLoading(false);
130
+ }
131
+ };
132
+
133
+ const topics = Array.from(topicMap.entries())
134
+ .map(([name, { count, pageIds }]) => ({
135
+ name,
136
+ count,
137
+ pageIds,
138
+ }))
139
+ .sort((a, b) => a.name.localeCompare(b.name));
140
+
141
+ const filteredPages = validPages.filter((page) => {
142
+ if (excludedIds.includes(page.id)) {
143
+ return false;
144
+ }
145
+ if (selectedTopics.length === 0) {
146
+ return true;
147
+ }
148
+ return (
149
+ page.topics && page.topics.some((topic) => selectedTopics.includes(topic))
150
+ );
151
+ });
152
+
153
+ const sortedPages = [...filteredPages].sort((a, b) => {
154
+ if (selectedMode === 'popular') {
155
+ const aViews = analyticsData[a.id]?.total_actions || 0;
156
+ const bViews = analyticsData[b.id]?.total_actions || 0;
157
+ return bViews - aViews;
158
+ }
159
+ const bDate = b.changed ? new Date(b.changed) : new Date(0);
160
+ const aDate = a.changed ? new Date(a.changed) : new Date(0);
161
+ return bDate.getTime() - aDate.getTime();
162
+ });
163
+
164
+ const totalPages = Math.ceil(sortedPages.length / PER_PAGE);
165
+ const paginatedPages = sortedPages.slice(
166
+ (currentPage - 1) * PER_PAGE,
167
+ currentPage * PER_PAGE
168
+ );
169
+
170
+ const updatePaneNode = () => {
171
+ if (nodeId) {
172
+ const allNodes = ctx.allNodes.get();
173
+ const paneNode = cloneDeep(allNodes.get(nodeId)) as PaneNode;
174
+ if (paneNode) {
175
+ const updatedNode = {
176
+ ...paneNode,
177
+ codeHookTarget: 'list-content',
178
+ codeHookPayload: {
179
+ options: JSON.stringify({
180
+ defaultMode: selectedMode,
181
+ excludedIds: excludedIds.join(','),
182
+ topics: selectedTopics.join(','),
183
+ pageSize: pageSize,
184
+ bgColor: bgColor,
185
+ }),
186
+ },
187
+ bgColour: bgColor || undefined,
188
+ isChanged: true,
189
+ };
190
+
191
+ // If bgColor is empty, remove the property
192
+ if (!bgColor) {
193
+ delete updatedNode.bgColour;
194
+ }
195
+
196
+ ctx.modifyNodes([updatedNode]);
197
+ }
198
+ }
199
+ };
200
+
201
+ useEffect(() => {
202
+ fetchAnalyticsData();
203
+ }, []);
204
+
205
+ useEffect(() => {
206
+ if (isInitialMount.current) {
207
+ isInitialMount.current = false;
208
+ return;
209
+ }
210
+
211
+ const timeoutId = setTimeout(() => {
212
+ updatePaneNode();
213
+ }, 500);
214
+
215
+ return () => clearTimeout(timeoutId);
216
+ }, [selectedMode, excludedIds, selectedTopics, pageSize, bgColor]);
217
+
218
+ // Toggle a page's exclusion status
219
+ const toggleExclude = (id: string) => {
220
+ setExcludedIds((prev) =>
221
+ prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]
222
+ );
223
+ };
224
+
225
+ // Toggle a topic's selection status
226
+ const toggleTopicFilter = (topic: string) => {
227
+ setSelectedTopics((prev) =>
228
+ prev.includes(topic) ? prev.filter((t) => t !== topic) : [...prev, topic]
229
+ );
230
+ };
231
+
232
+ // Handle pagination
233
+ const handlePageChange = (direction: 'prev' | 'next') => {
234
+ if (direction === 'prev' && currentPage > 1) {
235
+ setCurrentPage(currentPage - 1);
236
+ } else if (direction === 'next' && currentPage < totalPages) {
237
+ setCurrentPage(currentPage + 1);
238
+ }
239
+ };
240
+
241
+ // Exclude all pages with a specific topic
242
+ const excludeAllWithTopic = (topicName: string) => {
243
+ // Get all page IDs with this topic
244
+ const pageIdsToExclude = validPages
245
+ .filter((page) => page.topics && page.topics.includes(topicName))
246
+ .map((page) => page.id);
247
+
248
+ // Add them to excluded IDs
249
+ setExcludedIds((prev) => {
250
+ const newExcluded = Array.from(new Set([...prev, ...pageIdsToExclude]));
251
+ return newExcluded;
252
+ });
253
+
254
+ // Also remove the topic from selected topics to maintain consistency
255
+ setSelectedTopics((prev) => prev.filter((t) => t !== topicName));
256
+ };
257
+
258
+ // Add all pages with a topic and remove from excluded
259
+ const includeAllWithTopic = (topicName: string) => {
260
+ const topicInfo = topicMap.get(topicName);
261
+ if (!topicInfo) return;
262
+
263
+ // Remove these pages from excluded IDs
264
+ setExcludedIds((prev) =>
265
+ prev.filter((id) => !topicInfo.pageIds.includes(id))
266
+ );
267
+
268
+ // Ensure the topic is selected
269
+ if (!selectedTopics.includes(topicName)) {
270
+ setSelectedTopics((prev) => [...prev, topicName]);
271
+ }
272
+ };
273
+
274
+ // Update page size
275
+ const handlePageSizeChange = (value: number) => {
276
+ setPageSize(value);
277
+ setCurrentPage(1); // Reset to first page when changing page size
278
+ };
279
+
280
+ // Check if a topic is effectively selected (in selectedTopics and no pages with this topic are excluded)
281
+ const isTopicEffectivelySelected = (topicName: string) => {
282
+ const topicInfo = topicMap.get(topicName);
283
+ if (!topicInfo) return false;
284
+
285
+ return (
286
+ selectedTopics.includes(topicName) &&
287
+ !topicInfo.pageIds.some((id) => excludedIds.includes(id))
288
+ );
289
+ };
290
+
291
+ // Check if topic is partially excluded (some pages with this topic are excluded)
292
+ const isTopicPartiallyExcluded = (topicName: string) => {
293
+ const topicInfo = topicMap.get(topicName);
294
+ if (!topicInfo) return false;
295
+
296
+ const excludedCount = topicInfo.pageIds.filter((id) =>
297
+ excludedIds.includes(id)
298
+ ).length;
299
+ return excludedCount > 0 && excludedCount < topicInfo.pageIds.length;
300
+ };
301
+
302
+ // Check if all pages with this topic are excluded
303
+ const isTopicFullyExcluded = (topicName: string) => {
304
+ const topicInfo = topicMap.get(topicName);
305
+ if (!topicInfo) return false;
306
+
307
+ return topicInfo.pageIds.every((id) => excludedIds.includes(id));
308
+ };
309
+
310
+ // If panel is not open, show only the configuration button
311
+ if (!isPanelOpen) {
312
+ return (
313
+ <div className="flex min-h-[200px] w-full flex-col items-center justify-center space-y-6 rounded-lg bg-slate-50 p-6">
314
+ <button
315
+ onClick={() => setIsPanelOpen(true)}
316
+ className="rounded-lg bg-cyan-600 px-6 py-3 font-bold text-white shadow-md transition-colors hover:bg-cyan-700"
317
+ >
318
+ {hasConfiguration
319
+ ? 'Edit Content List Widget'
320
+ : 'Configure Content List Widget'}
321
+ </button>
322
+ {hasConfiguration && (
323
+ <div className="mt-3 text-sm text-gray-600">
324
+ {selectedTopics.length > 0 ? (
325
+ <span>
326
+ Showing content with topics: {selectedTopics.join(', ')}
327
+ </span>
328
+ ) : (
329
+ <span>
330
+ Showing {filteredPages.length} pages, {pageSize} per page
331
+ </span>
332
+ )}
333
+ {excludedIds.length > 0 && (
334
+ <span>, {excludedIds.length} pages excluded</span>
335
+ )}
336
+ </div>
337
+ )}
338
+ </div>
339
+ );
340
+ }
341
+
342
+ if (isAnalyticsLoading) return null;
343
+ return (
344
+ <div className="w-full space-y-6 bg-slate-50 p-6">
345
+ <div className="flex items-center justify-between">
346
+ <h2 className="text-xl font-bold text-gray-900">
347
+ Configure Content List
348
+ </h2>
349
+ <button
350
+ onClick={() => setIsPanelOpen(false)}
351
+ className="rounded bg-gray-200 px-4 py-2 font-bold text-gray-800 transition-colors hover:bg-gray-300"
352
+ >
353
+ Close Configuration
354
+ </button>
355
+ </div>
356
+
357
+ <div className="rounded-lg bg-white p-4 shadow">
358
+ <div className="border-b border-gray-200 pb-4">
359
+ <h3 className="text-lg font-bold text-gray-900">Content Settings</h3>
360
+ </div>
361
+ <div className="space-y-4 pt-4">
362
+ <div>
363
+ <label
364
+ htmlFor="page-size"
365
+ className="block text-sm font-bold text-gray-700"
366
+ >
367
+ Items per page
368
+ </label>
369
+ <select
370
+ id="page-size"
371
+ name="page-size"
372
+ className="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-cyan-600 focus:outline-none focus:ring-cyan-600 sm:text-sm"
373
+ value={pageSize}
374
+ onChange={(e) => handlePageSizeChange(parseInt(e.target.value))}
375
+ >
376
+ <option value="5">5 items</option>
377
+ <option value="10">10 items</option>
378
+ <option value="15">15 items</option>
379
+ <option value="20">20 items</option>
380
+ </select>
381
+ </div>
382
+
383
+ <div>
384
+ <label
385
+ htmlFor="sort-mode"
386
+ className="block text-sm font-bold text-gray-700"
387
+ >
388
+ Default sort order
389
+ </label>
390
+ <select
391
+ id="sort-mode"
392
+ name="sort-mode"
393
+ className="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-cyan-600 focus:outline-none focus:ring-cyan-600 sm:text-sm"
394
+ value={selectedMode}
395
+ onChange={(e) => setSelectedMode(e.target.value)}
396
+ >
397
+ <option value="recent">Most Recent</option>
398
+ <option value="popular">Most Popular</option>
399
+ </select>
400
+ <p className="mt-1 text-xs text-gray-500">
401
+ Note: Users can toggle between views regardless of the default
402
+ setting
403
+ </p>
404
+ </div>
405
+
406
+ <div>
407
+ <ColorPickerCombo
408
+ title="Background Color"
409
+ defaultColor={bgColor}
410
+ onColorChange={(color: string) => setBgColor(color)}
411
+ config={config!}
412
+ allowNull={true}
413
+ />
414
+ <p className="mt-1 text-xs text-gray-500">
415
+ Set a background color for the content list section
416
+ </p>
417
+ </div>
418
+ </div>
419
+ </div>
420
+
421
+ {topics.length > 0 && (
422
+ <div className="overflow-hidden rounded-lg bg-white shadow">
423
+ <div className="border-b border-gray-200 px-4 py-5">
424
+ <h3 className="text-lg font-bold leading-6 text-gray-900">
425
+ Topics
426
+ </h3>
427
+ <p className="mt-1 text-sm text-gray-500">
428
+ Select topics to filter content
429
+ </p>
430
+ <div className="mt-3 flex space-x-2">
431
+ <button
432
+ onClick={() => {
433
+ // Only select topics that have valid pages
434
+ const validTopics = topics
435
+ .filter((t) =>
436
+ t.pageIds.some((id) =>
437
+ validPages.some((page) => page.id === id)
438
+ )
439
+ )
440
+ .map((t) => t.name);
441
+
442
+ setSelectedTopics(validTopics);
443
+ // Clear exclusions for all topics
444
+ const allTopicPageIds = topics.flatMap((t) => t.pageIds);
445
+ setExcludedIds((prev) =>
446
+ prev.filter((id) => !allTopicPageIds.includes(id))
447
+ );
448
+ }}
449
+ className="rounded-md bg-cyan-600/10 px-3 py-1 text-sm font-bold text-cyan-600 hover:bg-cyan-600/20"
450
+ >
451
+ Include All Topics
452
+ </button>
453
+ <button
454
+ onClick={() => setSelectedTopics([])}
455
+ className="rounded-md bg-gray-100 px-3 py-1 text-sm font-bold text-gray-700 hover:bg-gray-200"
456
+ >
457
+ Clear All
458
+ </button>
459
+ </div>
460
+ </div>
461
+ <div className="divide-y divide-gray-200">
462
+ {topics.map((topic) => (
463
+ <div
464
+ key={topic.name}
465
+ className="flex items-center justify-between p-4"
466
+ >
467
+ <div className="flex items-center">
468
+ <span className="text-sm font-bold text-gray-900">
469
+ {topic.name}
470
+ </span>
471
+ <span className="ml-2 text-sm text-gray-500">
472
+ ({topic.count} pages)
473
+ </span>
474
+ {isTopicEffectivelySelected(topic.name) && (
475
+ <span className="ml-2 inline-flex items-center rounded-full bg-cyan-600/10 px-2 py-0.5 text-xs font-bold text-cyan-600">
476
+ Included
477
+ </span>
478
+ )}
479
+ {isTopicPartiallyExcluded(topic.name) && (
480
+ <span className="ml-2 inline-flex items-center rounded-full bg-yellow-100 px-2 py-0.5 text-xs font-bold text-yellow-800">
481
+ Partially Excluded
482
+ </span>
483
+ )}
484
+ {isTopicFullyExcluded(topic.name) && (
485
+ <span className="ml-2 inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-bold text-red-800">
486
+ Excluded
487
+ </span>
488
+ )}
489
+ </div>
490
+ <div className="flex space-x-2">
491
+ <button
492
+ onClick={() => includeAllWithTopic(topic.name)}
493
+ className={`px-2 py-1 text-xs font-bold ${
494
+ isTopicEffectivelySelected(topic.name)
495
+ ? 'cursor-not-allowed bg-gray-100 text-gray-400'
496
+ : 'bg-cyan-600/10 text-cyan-600 hover:bg-cyan-600/20 hover:text-cyan-700'
497
+ } rounded-md`}
498
+ disabled={isTopicEffectivelySelected(topic.name)}
499
+ >
500
+ Include All
501
+ </button>
502
+ <button
503
+ onClick={() => excludeAllWithTopic(topic.name)}
504
+ className={`px-2 py-1 text-xs font-bold ${
505
+ isTopicFullyExcluded(topic.name)
506
+ ? 'cursor-not-allowed bg-gray-100 text-gray-400'
507
+ : 'bg-red-100 text-red-600 hover:bg-red-200 hover:text-red-800'
508
+ } rounded-md`}
509
+ disabled={isTopicFullyExcluded(topic.name)}
510
+ >
511
+ Exclude All
512
+ </button>
513
+ </div>
514
+ </div>
515
+ ))}
516
+ </div>
517
+ </div>
518
+ )}
519
+
520
+ <div className="overflow-hidden rounded-lg bg-white shadow">
521
+ <div className="border-b border-gray-200 px-4 py-5">
522
+ <h3 className="text-lg font-bold leading-6 text-gray-900">
523
+ Matching Pages
524
+ </h3>
525
+ <p className="mt-1 text-sm text-gray-500">
526
+ Pages matching selected topics ({paginatedPages.length} of{' '}
527
+ {filteredPages.length})
528
+ </p>
529
+ </div>
530
+ {paginatedPages.length === 0 ? (
531
+ <div className="p-8 text-center text-gray-500">
532
+ No pages match the selected criteria. Try selecting different topics
533
+ or removing exclusions.
534
+ </div>
535
+ ) : (
536
+ <div className="divide-y divide-gray-200">
537
+ {paginatedPages.map((page) => {
538
+ const isExcluded = excludedIds.includes(page.id);
539
+ const analytics = analyticsData[page.id];
540
+
541
+ return (
542
+ <div key={page.id} className="flex items-center p-4">
543
+ <div className="relative h-16 w-24 flex-shrink-0">
544
+ <img
545
+ src={page.thumbSrc}
546
+ srcSet={page.thumbSrcSet}
547
+ alt={page.title}
548
+ className="absolute inset-0 h-full w-full rounded object-cover"
549
+ />
550
+ </div>
551
+ <div className="ml-4 min-w-0 flex-1">
552
+ <div className="flex items-center justify-between">
553
+ <p className="truncate text-sm font-bold text-gray-900">
554
+ {page.title}
555
+ </p>
556
+ <div className="flex items-center space-x-4">
557
+ <button
558
+ onClick={() => toggleExclude(page.id)}
559
+ className={`rounded px-2 py-1 text-xs font-bold ${
560
+ isExcluded
561
+ ? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
562
+ : 'bg-red-100 text-red-600 hover:bg-red-200'
563
+ }`}
564
+ >
565
+ {isExcluded ? 'Restore' : 'Exclude'}
566
+ </button>
567
+ </div>
568
+ </div>
569
+ <div className="mt-1">
570
+ <p className="line-clamp-1 text-sm text-gray-500">
571
+ {page.description}
572
+ </p>
573
+ </div>
574
+ {page.topics && page.topics.length > 0 && (
575
+ <div className="mt-1 flex flex-wrap gap-1">
576
+ {page.topics.map((topic) => (
577
+ <span
578
+ key={topic}
579
+ onClick={() => toggleTopicFilter(topic)}
580
+ className={`inline-flex cursor-pointer items-center rounded-full px-2 py-0.5 text-xs font-bold ${
581
+ selectedTopics.includes(topic)
582
+ ? 'bg-cyan-600/10 text-cyan-600'
583
+ : 'bg-gray-100 text-gray-800'
584
+ }`}
585
+ >
586
+ {topic}
587
+ </span>
588
+ ))}
589
+ </div>
590
+ )}
591
+ <div className="mt-1 flex items-center text-xs text-gray-500">
592
+ {analytics && (
593
+ <>
594
+ <span>{analytics.total_actions} views</span>
595
+ {page.changed && (
596
+ <>
597
+ <span className="mx-2">•</span>
598
+ <span>
599
+ Updated{' '}
600
+ {new Date(page.changed).toLocaleDateString()}
601
+ </span>
602
+ </>
603
+ )}
604
+ </>
605
+ )}
606
+ </div>
607
+ </div>
608
+ </div>
609
+ );
610
+ })}
611
+ </div>
612
+ )}
613
+ {totalPages > 1 && (
614
+ <div className="flex justify-between px-4 py-3">
615
+ <button
616
+ onClick={() => handlePageChange('prev')}
617
+ disabled={currentPage === 1}
618
+ className={classNames(
619
+ 'rounded-md px-4 py-2 text-sm font-bold',
620
+ currentPage === 1
621
+ ? 'cursor-not-allowed bg-gray-200 text-gray-500'
622
+ : 'bg-cyan-600 text-white hover:bg-cyan-700'
623
+ )}
624
+ >
625
+ Previous
626
+ </button>
627
+ <span className="text-sm text-gray-700">
628
+ Page {currentPage} of {totalPages}
629
+ </span>
630
+ <button
631
+ onClick={() => handlePageChange('next')}
632
+ disabled={currentPage === totalPages}
633
+ className={classNames(
634
+ 'rounded-md px-4 py-2 text-sm font-bold',
635
+ currentPage === totalPages
636
+ ? 'cursor-not-allowed bg-gray-200 text-gray-500'
637
+ : 'bg-cyan-600 text-white hover:bg-cyan-700'
638
+ )}
639
+ >
640
+ Next
641
+ </button>
642
+ </div>
643
+ )}
644
+ </div>
645
+ </div>
646
+ );
647
+ };
648
+
649
+ export default ListContentSetup;