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,397 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useFormState } from '@/hooks/useFormState';
3
+ import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
4
+ import TrashIcon from '@heroicons/react/24/outline/TrashIcon';
5
+ import StringInput from '@/components/form/StringInput';
6
+ import BooleanToggle from '@/components/form/BooleanToggle';
7
+ import NumberInput from '@/components/form/NumberInput';
8
+ import EnumSelect from '@/components/form/EnumSelect';
9
+ import UnsavedChangesBar from '@/components/form/UnsavedChangesBar';
10
+ import {
11
+ getBrandConfig,
12
+ saveBrandConfigWithStateUpdate,
13
+ } from '@/utils/api/brandConfig';
14
+ import { convertToLocalState } from '@/utils/api/brandHelpers';
15
+ import type {
16
+ FieldDefinition,
17
+ FullContentMapItem,
18
+ FieldErrors,
19
+ BrandConfig,
20
+ } from '@/types/tractstack';
21
+
22
+ interface KnownResourceFormProps {
23
+ categorySlug: string;
24
+ contentMap: FullContentMapItem[];
25
+ onClose?: (saved: boolean) => void;
26
+ }
27
+
28
+ interface KnownResourceState {
29
+ categorySlug: string;
30
+ fields: Record<string, FieldDefinition>;
31
+ }
32
+
33
+ const FIELD_TYPES = [
34
+ { value: 'string', label: 'Text' },
35
+ { value: 'number', label: 'Number' },
36
+ { value: 'boolean', label: 'True/False' },
37
+ { value: 'multi', label: 'Multiple Values' },
38
+ { value: 'date', label: 'Date/Time' },
39
+ { value: 'image', label: 'Image' },
40
+ ];
41
+
42
+ const KnownResourceForm = ({
43
+ categorySlug,
44
+ contentMap,
45
+ onClose,
46
+ }: KnownResourceFormProps) => {
47
+ const [newFieldName, setNewFieldName] = useState('');
48
+ const [showAddField, setShowAddField] = useState(false);
49
+ const [brandConfig, setBrandConfig] = useState<BrandConfig | null>(null);
50
+ const [loading, setLoading] = useState(false);
51
+
52
+ useEffect(() => {
53
+ if (!brandConfig && !loading) {
54
+ setLoading(true);
55
+ getBrandConfig(window.TRACTSTACK_CONFIG?.tenantId || 'default')
56
+ .then(setBrandConfig)
57
+ .catch(console.error)
58
+ .finally(() => setLoading(false));
59
+ }
60
+ }, [brandConfig, loading]);
61
+
62
+ const knownResources = brandConfig?.KNOWN_RESOURCES || {};
63
+ const isCreate = categorySlug === 'new';
64
+ const currentCategory = isCreate ? {} : knownResources[categorySlug] || {};
65
+
66
+ const hasExistingResources =
67
+ !isCreate && contentMap.some((item) => item.categorySlug === categorySlug);
68
+
69
+ const initialState: KnownResourceState = {
70
+ categorySlug: isCreate ? '' : categorySlug,
71
+ fields: isCreate ? {} : currentCategory,
72
+ };
73
+
74
+ const validator = (state: KnownResourceState): FieldErrors => {
75
+ const errors: FieldErrors = {};
76
+
77
+ if (!state.categorySlug.trim()) {
78
+ errors.categorySlug = 'Category name is required';
79
+ } else if (!/^[a-z0-9-]+$/.test(state.categorySlug)) {
80
+ errors.categorySlug = 'Category name must be lowercase with hyphens only';
81
+ } else if (isCreate && knownResources[state.categorySlug]) {
82
+ errors.categorySlug = 'Category name already exists';
83
+ }
84
+
85
+ return errors;
86
+ };
87
+
88
+ const formState = useFormState<KnownResourceState>({
89
+ initialData: initialState,
90
+ validator,
91
+ onSave: async (data) => {
92
+ try {
93
+ // Update known resources in brand config
94
+ if (!brandConfig) throw new Error('Brand config not loaded');
95
+ const brandState = convertToLocalState(brandConfig);
96
+ const updatedKnownResources = {
97
+ ...brandState.knownResources,
98
+ [data.categorySlug]: data.fields,
99
+ };
100
+
101
+ const updatedBrandState = {
102
+ ...brandState,
103
+ knownResources: updatedKnownResources,
104
+ };
105
+
106
+ await saveBrandConfigWithStateUpdate(
107
+ window.TRACTSTACK_CONFIG?.tenantId || 'default',
108
+ updatedBrandState
109
+ );
110
+
111
+ // Call success callback after save (original pattern)
112
+ setTimeout(() => {
113
+ onClose?.(true);
114
+ }, 1000);
115
+
116
+ return data;
117
+ } catch (error) {
118
+ console.error('Known resource save failed:', error);
119
+ throw error;
120
+ }
121
+ },
122
+ unsavedChanges: {
123
+ enableBrowserWarning: true,
124
+ browserWarningMessage: 'Your resource category changes will be lost!',
125
+ },
126
+ });
127
+
128
+ const addNewField = () => {
129
+ if (!newFieldName.trim()) return;
130
+
131
+ const fieldName = newFieldName.trim();
132
+ if (formState.state.fields[fieldName]) return;
133
+
134
+ const newField: FieldDefinition = {
135
+ type: 'string',
136
+ optional: true,
137
+ };
138
+
139
+ formState.updateField('fields', {
140
+ ...formState.state.fields,
141
+ [fieldName]: newField,
142
+ });
143
+
144
+ setNewFieldName('');
145
+ setShowAddField(false);
146
+ };
147
+
148
+ const removeField = (fieldName: string) => {
149
+ const updatedFields = { ...formState.state.fields };
150
+ delete updatedFields[fieldName];
151
+ formState.updateField('fields', updatedFields);
152
+ };
153
+
154
+ const updateField = (
155
+ fieldName: string,
156
+ updates: Partial<FieldDefinition>
157
+ ) => {
158
+ const updatedFields = {
159
+ ...formState.state.fields,
160
+ [fieldName]: {
161
+ ...formState.state.fields[fieldName],
162
+ ...updates,
163
+ },
164
+ };
165
+ formState.updateField('fields', updatedFields);
166
+ };
167
+
168
+ const isFieldLocked = (fieldName: string): boolean => {
169
+ return hasExistingResources && currentCategory[fieldName] !== undefined;
170
+ };
171
+
172
+ const availableCategories = Object.keys(knownResources).filter(
173
+ (cat) => cat !== categorySlug
174
+ );
175
+
176
+ const handleCancel = () => {
177
+ onClose?.(false);
178
+ };
179
+
180
+ return (
181
+ <div className="space-y-8">
182
+ {/* Header */}
183
+ <div className="border-b border-gray-200 pb-4">
184
+ <h2 className="text-2xl font-bold text-gray-900">
185
+ {isCreate ? 'Create Resource Category' : `Edit ${categorySlug}`}
186
+ </h2>
187
+ <p className="mt-2 text-sm text-gray-600">
188
+ {isCreate
189
+ ? 'Create a new resource category with custom fields.'
190
+ : 'Edit the resource category configuration.'}
191
+ </p>
192
+ {hasExistingResources && (
193
+ <div className="mt-2 rounded-md bg-yellow-50 p-3">
194
+ <p className="text-sm text-yellow-700">
195
+ ⚠️ This category has existing resources. Existing fields cannot be
196
+ modified.
197
+ </p>
198
+ </div>
199
+ )}
200
+ </div>
201
+
202
+ <div className="space-y-6">
203
+ {/* Category Name */}
204
+ <StringInput
205
+ label="Category Name"
206
+ value={formState.state.categorySlug}
207
+ onChange={(value) => formState.updateField('categorySlug', value)}
208
+ disabled={!isCreate}
209
+ error={formState.errors.categorySlug}
210
+ placeholder="e.g., people, vehicles, locations"
211
+ required
212
+ />
213
+ <p className="text-sm text-gray-500">
214
+ Must be lowercase with hyphens. Cannot be changed after creation.
215
+ </p>
216
+
217
+ {/* Fields Section */}
218
+ <div className="space-y-6">
219
+ <div className="flex items-center justify-between">
220
+ <h3 className="text-lg font-bold text-gray-900">Fields</h3>
221
+ <button
222
+ type="button"
223
+ onClick={() => setShowAddField(true)}
224
+ className="inline-flex items-center rounded-md bg-cyan-600 px-3 py-2 text-sm font-bold text-white shadow-sm hover:bg-cyan-500"
225
+ >
226
+ <PlusIcon className="mr-2 h-4 w-4" />
227
+ Add Field
228
+ </button>
229
+ </div>
230
+
231
+ {/* Add Field Form */}
232
+ {showAddField && (
233
+ <div className="rounded-lg border border-gray-200 bg-gray-50 p-4">
234
+ <h4 className="mb-3 text-sm font-bold text-gray-900">
235
+ Add New Field
236
+ </h4>
237
+ <div className="flex gap-3">
238
+ <div className="flex-1">
239
+ <StringInput
240
+ value={newFieldName}
241
+ onChange={setNewFieldName}
242
+ placeholder="Field name (e.g., description, price)"
243
+ />
244
+ </div>
245
+ <button
246
+ type="button"
247
+ onClick={addNewField}
248
+ disabled={!newFieldName.trim()}
249
+ className="rounded-md bg-cyan-600 px-3 py-2 text-sm font-bold text-white hover:bg-cyan-500 disabled:opacity-50"
250
+ >
251
+ Add
252
+ </button>
253
+ <button
254
+ type="button"
255
+ onClick={() => {
256
+ setShowAddField(false);
257
+ setNewFieldName('');
258
+ }}
259
+ className="rounded-md bg-gray-300 px-3 py-2 text-sm font-bold text-gray-700 hover:bg-gray-400"
260
+ >
261
+ Cancel
262
+ </button>
263
+ </div>
264
+ </div>
265
+ )}
266
+
267
+ {/* Existing Fields */}
268
+ {Object.keys(formState.state.fields).length === 0 ? (
269
+ <div className="py-6 text-center text-gray-500">
270
+ No fields defined yet. Click "Add Field" to create your first
271
+ field.
272
+ </div>
273
+ ) : (
274
+ <div className="space-y-4">
275
+ {Object.entries(formState.state.fields).map(
276
+ ([fieldName, fieldDef]) => {
277
+ const locked = isFieldLocked(fieldName);
278
+
279
+ return (
280
+ <div
281
+ key={fieldName}
282
+ className={`rounded-lg border p-4 ${
283
+ locked
284
+ ? 'border-gray-300 bg-gray-50'
285
+ : 'border-gray-200 bg-white'
286
+ }`}
287
+ >
288
+ <div className="mb-4 flex items-center justify-between">
289
+ <h5 className="text-sm font-bold text-gray-900">
290
+ {fieldName}
291
+ {locked && (
292
+ <span className="ml-2 text-xs text-gray-500">
293
+ (locked)
294
+ </span>
295
+ )}
296
+ </h5>
297
+ {!locked && (
298
+ <button
299
+ type="button"
300
+ onClick={() => removeField(fieldName)}
301
+ className="text-red-600 hover:text-red-800"
302
+ >
303
+ <TrashIcon className="h-4 w-4" />
304
+ </button>
305
+ )}
306
+ </div>
307
+
308
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
309
+ <EnumSelect
310
+ label="Type"
311
+ value={fieldDef.type}
312
+ onChange={(value) =>
313
+ updateField(fieldName, { type: value as any })
314
+ }
315
+ options={FIELD_TYPES}
316
+ disabled={locked}
317
+ />
318
+
319
+ <BooleanToggle
320
+ label="Optional"
321
+ value={fieldDef.optional || false}
322
+ onChange={(value) =>
323
+ updateField(fieldName, { optional: value })
324
+ }
325
+ disabled={locked}
326
+ />
327
+
328
+ {fieldDef.type === 'categoryReference' && (
329
+ <EnumSelect
330
+ label="Reference Category"
331
+ value={fieldDef.belongsToCategory || ''}
332
+ onChange={(value) =>
333
+ updateField(fieldName, {
334
+ belongsToCategory: value,
335
+ })
336
+ }
337
+ options={availableCategories.map((cat) => ({
338
+ value: cat,
339
+ label: cat,
340
+ }))}
341
+ disabled={locked}
342
+ />
343
+ )}
344
+
345
+ {fieldDef.type === 'number' && (
346
+ <>
347
+ <NumberInput
348
+ label="Min Value"
349
+ value={fieldDef.minNumber || 0}
350
+ onChange={(value) =>
351
+ updateField(fieldName, { minNumber: value })
352
+ }
353
+ disabled={locked}
354
+ />
355
+ <NumberInput
356
+ label="Max Value"
357
+ value={fieldDef.maxNumber || 100}
358
+ onChange={(value) =>
359
+ updateField(fieldName, { maxNumber: value })
360
+ }
361
+ disabled={locked}
362
+ />
363
+ </>
364
+ )}
365
+ </div>
366
+ </div>
367
+ );
368
+ }
369
+ )}
370
+ </div>
371
+ )}
372
+ </div>
373
+ </div>
374
+
375
+ {/* Save/Cancel Bar */}
376
+ <UnsavedChangesBar
377
+ formState={formState}
378
+ message="You have unsaved resource category changes"
379
+ saveLabel="Save Category"
380
+ cancelLabel="Discard Changes"
381
+ />
382
+
383
+ {/* Cancel Navigation Button */}
384
+ <div className="flex justify-start">
385
+ <button
386
+ type="button"
387
+ onClick={handleCancel}
388
+ className="text-sm font-bold text-gray-600 hover:text-gray-800"
389
+ >
390
+ ← Back to Resource Categories
391
+ </button>
392
+ </div>
393
+ </div>
394
+ );
395
+ };
396
+
397
+ export default KnownResourceForm;
@@ -0,0 +1,260 @@
1
+ import { useState, useEffect } from 'react';
2
+ import PlusIcon from '@heroicons/react/24/outline/PlusIcon';
3
+ import PencilIcon from '@heroicons/react/24/outline/PencilIcon';
4
+ import TrashIcon from '@heroicons/react/24/outline/TrashIcon';
5
+ import {
6
+ getBrandConfig,
7
+ saveBrandConfigWithStateUpdate,
8
+ } from '@/utils/api/brandConfig';
9
+ import { convertToLocalState } from '@/utils/api/brandHelpers';
10
+ import ResourceBulkIngest from './ResourceBulkIngest';
11
+ import type { BrandConfig, FullContentMapItem } from '@/types/tractstack';
12
+ import type { MouseEvent } from 'react';
13
+
14
+ interface KnownResourceTableProps {
15
+ contentMap: FullContentMapItem[];
16
+ onEdit: (categorySlug: string) => void;
17
+ onRefresh?: () => void;
18
+ }
19
+
20
+ const KnownResourceTable = ({
21
+ contentMap,
22
+ onEdit,
23
+ onRefresh,
24
+ }: KnownResourceTableProps) => {
25
+ const [searchTerm, setSearchTerm] = useState('');
26
+ const [isDeleting, setIsDeleting] = useState<string | null>(null);
27
+ const [showBulkIngest, setShowBulkIngest] = useState(false);
28
+ const [brandConfig, setBrandConfig] = useState<BrandConfig | null>(null);
29
+ const [loading, setLoading] = useState(false);
30
+
31
+ useEffect(() => {
32
+ if (!brandConfig && !loading) {
33
+ setLoading(true);
34
+ getBrandConfig(window.TRACTSTACK_CONFIG?.tenantId || 'default')
35
+ .then(setBrandConfig)
36
+ .catch(console.error)
37
+ .finally(() => setLoading(false));
38
+ }
39
+ }, [brandConfig, loading]);
40
+
41
+ const knownResources = brandConfig?.KNOWN_RESOURCES || {};
42
+
43
+ const getResourceCount = (categorySlug: string): number => {
44
+ return contentMap.filter((item) => item.categorySlug === categorySlug)
45
+ .length;
46
+ };
47
+
48
+ const filteredCategories = Object.keys(knownResources).filter((category) =>
49
+ category.toLowerCase().includes(searchTerm.toLowerCase())
50
+ );
51
+
52
+ const handleCreateNew = () => {
53
+ onEdit('new');
54
+ };
55
+
56
+ const handleDelete = async (categorySlug: string, event: MouseEvent) => {
57
+ event.stopPropagation();
58
+
59
+ const resourceCount = getResourceCount(categorySlug);
60
+
61
+ if (resourceCount > 0) {
62
+ alert(
63
+ `Cannot delete category "${categorySlug}": it has ${resourceCount} resource(s). Delete all resources in this category first.`
64
+ );
65
+ return;
66
+ }
67
+
68
+ const confirmed = window.confirm(
69
+ `Are you sure you want to delete the resource category "${categorySlug}"? This action cannot be undone.`
70
+ );
71
+
72
+ if (!confirmed) {
73
+ return;
74
+ }
75
+
76
+ setIsDeleting(categorySlug);
77
+ try {
78
+ if (!brandConfig) throw new Error('Brand config not loaded');
79
+
80
+ const brandState = convertToLocalState(brandConfig);
81
+ const updatedKnownResources = { ...brandState.knownResources };
82
+ delete updatedKnownResources[categorySlug];
83
+
84
+ const updatedBrandState = {
85
+ ...brandState,
86
+ knownResources: updatedKnownResources,
87
+ };
88
+
89
+ await saveBrandConfigWithStateUpdate(
90
+ window.TRACTSTACK_CONFIG?.tenantId || 'default',
91
+ updatedBrandState
92
+ );
93
+
94
+ // Refresh the data
95
+ onRefresh?.();
96
+ } catch (error) {
97
+ console.error('Delete failed:', error);
98
+ alert('Failed to delete resource category. Please try again.');
99
+ } finally {
100
+ setIsDeleting(null);
101
+ }
102
+ };
103
+
104
+ return (
105
+ <div className="space-y-6">
106
+ <div className="flex items-center justify-between">
107
+ <div>
108
+ <h2 className="text-2xl font-bold text-gray-900">
109
+ Resource Categories
110
+ </h2>
111
+ <p className="mt-1 text-sm text-gray-600">
112
+ Manage resource category schemas and field definitions
113
+ </p>
114
+ </div>
115
+ <div className="flex space-x-3">
116
+ <button
117
+ onClick={() => setShowBulkIngest(true)}
118
+ className="inline-flex items-center rounded-md bg-orange-600 px-3 py-2 text-sm font-bold text-white shadow-sm hover:bg-orange-500"
119
+ >
120
+ <PlusIcon className="-ml-0.5 mr-1.5 h-5 w-5" aria-hidden="true" />
121
+ Bulk Import
122
+ </button>
123
+ <button
124
+ onClick={handleCreateNew}
125
+ className="inline-flex items-center gap-x-2 rounded-md bg-cyan-600 px-3.5 py-2.5 text-sm font-bold text-white shadow-sm hover:bg-cyan-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-cyan-600"
126
+ >
127
+ <PlusIcon className="-ml-0.5 h-5 w-5" />
128
+ New Category
129
+ </button>
130
+ </div>
131
+ </div>
132
+
133
+ <div className="flex items-center gap-4">
134
+ <div className="flex-1">
135
+ <input
136
+ type="text"
137
+ placeholder="Search categories..."
138
+ value={searchTerm}
139
+ onChange={(e) => setSearchTerm(e.target.value)}
140
+ className="w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-cyan-700 focus:ring-cyan-700"
141
+ />
142
+ </div>
143
+ </div>
144
+
145
+ <div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
146
+ <table className="min-w-full divide-y divide-gray-300">
147
+ <thead className="bg-gray-50">
148
+ <tr>
149
+ <th className="px-6 py-3 text-left text-xs font-bold uppercase tracking-wide text-gray-500">
150
+ Category
151
+ </th>
152
+ <th className="px-6 py-3 text-left text-xs font-bold uppercase tracking-wide text-gray-500">
153
+ Resources
154
+ </th>
155
+ <th className="px-6 py-3 text-left text-xs font-bold uppercase tracking-wide text-gray-500">
156
+ Fields
157
+ </th>
158
+ <th className="relative px-6 py-3">
159
+ <span className="sr-only">Actions</span>
160
+ </th>
161
+ </tr>
162
+ </thead>
163
+ <tbody className="divide-y divide-gray-200 bg-white">
164
+ {filteredCategories.length === 0 ? (
165
+ <tr>
166
+ <td colSpan={4} className="px-6 py-12 text-center">
167
+ <div className="text-gray-500">
168
+ {Object.keys(knownResources).length === 0
169
+ ? 'No resource categories created yet'
170
+ : 'No categories match your search'}
171
+ </div>
172
+ </td>
173
+ </tr>
174
+ ) : (
175
+ filteredCategories.map((categorySlug) => {
176
+ const resourceCount = getResourceCount(categorySlug);
177
+ const fieldCount = Object.keys(
178
+ knownResources[categorySlug]
179
+ ).length;
180
+ const canDelete = resourceCount === 0;
181
+
182
+ return (
183
+ <tr
184
+ key={categorySlug}
185
+ className="cursor-pointer hover:bg-gray-50"
186
+ onClick={() => onEdit(categorySlug)}
187
+ >
188
+ <td className="whitespace-nowrap px-6 py-4 text-sm font-bold text-gray-900">
189
+ {categorySlug}
190
+ </td>
191
+ <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
192
+ {resourceCount}{' '}
193
+ {resourceCount === 1 ? 'resource' : 'resources'}
194
+ </td>
195
+ <td className="whitespace-nowrap px-6 py-4 text-sm text-gray-500">
196
+ {fieldCount} {fieldCount === 1 ? 'field' : 'fields'}
197
+ </td>
198
+ <td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-bold sm:pr-6">
199
+ <div className="flex items-center justify-end space-x-2">
200
+ {/* Edit button */}
201
+ <button
202
+ onClick={(e) => {
203
+ e.stopPropagation();
204
+ onEdit(categorySlug);
205
+ }}
206
+ className="text-cyan-600 hover:text-cyan-900"
207
+ title="Edit category"
208
+ disabled={isDeleting === categorySlug}
209
+ >
210
+ <PencilIcon className="h-5 w-5" />
211
+ </button>
212
+
213
+ {/* Delete button */}
214
+ <button
215
+ onClick={(e) => handleDelete(categorySlug, e)}
216
+ disabled={!canDelete || isDeleting === categorySlug}
217
+ title={
218
+ canDelete
219
+ ? 'Delete category'
220
+ : `Cannot delete: category has ${resourceCount} resource(s)`
221
+ }
222
+ className={`transition-colors ${
223
+ canDelete && isDeleting !== categorySlug
224
+ ? 'text-red-600 hover:text-red-900'
225
+ : 'cursor-not-allowed text-gray-300'
226
+ }`}
227
+ >
228
+ {isDeleting === categorySlug ? (
229
+ <div className="h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-red-600" />
230
+ ) : (
231
+ <TrashIcon className="h-5 w-5" />
232
+ )}
233
+ </button>
234
+ </div>
235
+ </td>
236
+ </tr>
237
+ );
238
+ })
239
+ )}
240
+ </tbody>
241
+ </table>
242
+ </div>
243
+
244
+ {showBulkIngest && (
245
+ <ResourceBulkIngest
246
+ fullContentMap={contentMap}
247
+ onClose={(saved: boolean) => {
248
+ setShowBulkIngest(false);
249
+ if (saved) {
250
+ onRefresh?.();
251
+ }
252
+ }}
253
+ onRefresh={onRefresh || (() => {})}
254
+ />
255
+ )}
256
+ </div>
257
+ );
258
+ };
259
+
260
+ export default KnownResourceTable;