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,306 @@
1
+ import type {
2
+ AllowedImageFormat,
3
+ ImageDimensions,
4
+ ImageProcessingResult,
5
+ } from '@/types/formTypes';
6
+
7
+ interface ValidationResult {
8
+ isValid: boolean;
9
+ error?: string;
10
+ }
11
+
12
+ /**
13
+ * Validates file size
14
+ */
15
+ export const validateFileSize = (
16
+ file: File,
17
+ maxSizeKB: number
18
+ ): ValidationResult => {
19
+ if (file.size > maxSizeKB * 1024) {
20
+ return {
21
+ isValid: false,
22
+ error: `File size must be less than ${maxSizeKB}KB (current: ${Math.round(file.size / 1024)}KB)`,
23
+ };
24
+ }
25
+ return { isValid: true };
26
+ };
27
+
28
+ /**
29
+ * Validates file type against allowed formats
30
+ */
31
+ export const validateFileType = (
32
+ file: File,
33
+ allowedFormats: AllowedImageFormat[]
34
+ ): ValidationResult => {
35
+ const fileExtension = file.name.toLowerCase().split('.').pop();
36
+ const mimeType = file.type.toLowerCase();
37
+
38
+ const formatChecks: Record<
39
+ AllowedImageFormat,
40
+ (ext: string, mime: string) => boolean
41
+ > = {
42
+ svg: (ext, mime) => ext === 'svg' || mime === 'image/svg+xml',
43
+ ico: (ext, mime) =>
44
+ ext === 'ico' ||
45
+ mime === 'image/x-icon' ||
46
+ mime === 'image/vnd.microsoft.icon',
47
+ png: (ext, mime) => ext === 'png' || mime === 'image/png',
48
+ jpg: (ext, mime) =>
49
+ ext === 'jpg' || ext === 'jpeg' || mime === 'image/jpeg',
50
+ jpeg: (ext, mime) =>
51
+ ext === 'jpg' || ext === 'jpeg' || mime === 'image/jpeg',
52
+ webp: (ext, mime) => ext === 'webp' || mime === 'image/webp',
53
+ gif: (ext, mime) => ext === 'gif' || mime === 'image/gif',
54
+ };
55
+
56
+ const isValid = allowedFormats.some((format) =>
57
+ formatChecks[format]?.(fileExtension || '', mimeType)
58
+ );
59
+
60
+ if (!isValid) {
61
+ return {
62
+ isValid: false,
63
+ error: `File type not allowed. Accepted formats: ${allowedFormats.join(', ')}`,
64
+ };
65
+ }
66
+
67
+ return { isValid: true };
68
+ };
69
+
70
+ /**
71
+ * Gets image dimensions from a file
72
+ */
73
+ export const getImageDimensions = (file: File): Promise<ImageDimensions> => {
74
+ return new Promise((resolve, reject) => {
75
+ const img = new Image();
76
+
77
+ img.onload = () => {
78
+ resolve({
79
+ width: img.width,
80
+ height: img.height,
81
+ });
82
+ URL.revokeObjectURL(img.src);
83
+ };
84
+
85
+ img.onerror = () => {
86
+ URL.revokeObjectURL(img.src);
87
+ reject(new Error('Failed to load image'));
88
+ };
89
+
90
+ img.src = URL.createObjectURL(file);
91
+ });
92
+ };
93
+
94
+ /**
95
+ * Validates image dimensions
96
+ */
97
+ export const validateImageDimensions = async (
98
+ file: File,
99
+ requiredDimensions: ImageDimensions
100
+ ): Promise<ValidationResult> => {
101
+ try {
102
+ const dimensions = await getImageDimensions(file);
103
+
104
+ if (
105
+ dimensions.width !== requiredDimensions.width ||
106
+ dimensions.height !== requiredDimensions.height
107
+ ) {
108
+ return {
109
+ isValid: false,
110
+ error: `Image must be ${requiredDimensions.width}x${requiredDimensions.height}px (got ${dimensions.width}x${dimensions.height}px)`,
111
+ };
112
+ }
113
+
114
+ return { isValid: true };
115
+ } catch (error) {
116
+ return {
117
+ isValid: false,
118
+ error: 'Failed to read image dimensions',
119
+ };
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Checks if a file is an SVG (which doesn't need canvas processing)
125
+ */
126
+ export const isSvgFile = (file: File): boolean => {
127
+ return (
128
+ file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg')
129
+ );
130
+ };
131
+
132
+ /**
133
+ * Converts file to base64 (for SVG files and simple conversions)
134
+ */
135
+ export const fileToBase64 = (file: File): Promise<string> => {
136
+ return new Promise((resolve, reject) => {
137
+ const reader = new FileReader();
138
+
139
+ reader.onload = (e) => {
140
+ const result = e.target?.result as string;
141
+ resolve(result);
142
+ };
143
+
144
+ reader.onerror = () => {
145
+ reject(new Error('Failed to read file'));
146
+ };
147
+
148
+ reader.readAsDataURL(file);
149
+ });
150
+ };
151
+
152
+ /**
153
+ * Resizes and crops an image to exact dimensions
154
+ * Uses smart cropping (center crop) to maintain aspect ratio
155
+ */
156
+ export const resizeAndCropImage = async (
157
+ file: File,
158
+ targetDimensions: ImageDimensions,
159
+ quality: number = 0.9
160
+ ): Promise<ImageProcessingResult> => {
161
+ // Handle SVG files differently
162
+ if (isSvgFile(file)) {
163
+ const base64 = await fileToBase64(file);
164
+ return {
165
+ processedFile: base64,
166
+ warnings: ['SVG files cannot be resized. Original file preserved.'],
167
+ originalDimensions: { width: 0, height: 0 }, // SVG dimensions are dynamic
168
+ finalDimensions: targetDimensions,
169
+ };
170
+ }
171
+
172
+ return new Promise(async (resolve, reject) => {
173
+ try {
174
+ const originalDimensions = await getImageDimensions(file);
175
+ const warnings: string[] = [];
176
+
177
+ // If already correct dimensions, just convert to base64
178
+ if (
179
+ originalDimensions.width === targetDimensions.width &&
180
+ originalDimensions.height === targetDimensions.height
181
+ ) {
182
+ const base64 = await fileToBase64(file);
183
+ resolve({
184
+ processedFile: base64,
185
+ warnings: [],
186
+ originalDimensions,
187
+ finalDimensions: targetDimensions,
188
+ });
189
+ return;
190
+ }
191
+
192
+ const canvas = document.createElement('canvas');
193
+ const ctx = canvas.getContext('2d');
194
+
195
+ if (!ctx) {
196
+ reject(new Error('Could not get canvas context'));
197
+ return;
198
+ }
199
+
200
+ const img = new Image();
201
+
202
+ img.onload = () => {
203
+ // Calculate scaling to fill target dimensions (will crop if needed)
204
+ const scaleX = targetDimensions.width / img.width;
205
+ const scaleY = targetDimensions.height / img.height;
206
+ const scale = Math.max(scaleX, scaleY); // Use larger scale to fill
207
+
208
+ // Calculate scaled dimensions
209
+ const scaledWidth = img.width * scale;
210
+ const scaledHeight = img.height * scale;
211
+
212
+ // Calculate crop offset to center the image
213
+ const cropX = (scaledWidth - targetDimensions.width) / 2;
214
+ const cropY = (scaledHeight - targetDimensions.height) / 2;
215
+
216
+ // Set canvas to target dimensions
217
+ canvas.width = targetDimensions.width;
218
+ canvas.height = targetDimensions.height;
219
+
220
+ // Draw image scaled and centered
221
+ ctx.drawImage(img, -cropX, -cropY, scaledWidth, scaledHeight);
222
+
223
+ // Add warning about resizing
224
+ if (
225
+ originalDimensions.width !== targetDimensions.width ||
226
+ originalDimensions.height !== targetDimensions.height
227
+ ) {
228
+ warnings.push(
229
+ `Image resized from ${originalDimensions.width}x${originalDimensions.height} to ${targetDimensions.width}x${targetDimensions.height}`
230
+ );
231
+ }
232
+
233
+ // Add warning about cropping if aspect ratios don't match
234
+ const originalAspect =
235
+ originalDimensions.width / originalDimensions.height;
236
+ const targetAspect = targetDimensions.width / targetDimensions.height;
237
+
238
+ if (Math.abs(originalAspect - targetAspect) > 0.01) {
239
+ warnings.push('Image was cropped to fit the required aspect ratio');
240
+ }
241
+
242
+ // Convert to base64 (use JPEG for photos, PNG for images with transparency)
243
+ const outputFormat = file.type.includes('png')
244
+ ? 'image/png'
245
+ : 'image/jpeg';
246
+ const processedFile = canvas.toDataURL(outputFormat, quality);
247
+
248
+ resolve({
249
+ processedFile,
250
+ warnings,
251
+ originalDimensions,
252
+ finalDimensions: targetDimensions,
253
+ });
254
+
255
+ URL.revokeObjectURL(img.src);
256
+ };
257
+
258
+ img.onerror = () => {
259
+ URL.revokeObjectURL(img.src);
260
+ reject(new Error('Failed to load image for processing'));
261
+ };
262
+
263
+ img.src = URL.createObjectURL(file);
264
+ } catch (error) {
265
+ reject(error);
266
+ }
267
+ });
268
+ };
269
+
270
+ /**
271
+ * Comprehensive file validation
272
+ */
273
+ export interface FileValidationOptions {
274
+ maxSizeKB?: number;
275
+ allowedFormats?: AllowedImageFormat[];
276
+ requiredDimensions?: ImageDimensions;
277
+ allowAnyImageWithWarning?: boolean;
278
+ }
279
+
280
+ export const validateFile = async (
281
+ file: File,
282
+ options: FileValidationOptions
283
+ ): Promise<ValidationResult> => {
284
+ // Validate file size
285
+ if (options.maxSizeKB) {
286
+ const sizeResult = validateFileSize(file, options.maxSizeKB);
287
+ if (!sizeResult.isValid) return sizeResult;
288
+ }
289
+
290
+ // Validate file type
291
+ if (options.allowedFormats && !options.allowAnyImageWithWarning) {
292
+ const typeResult = validateFileType(file, options.allowedFormats);
293
+ if (!typeResult.isValid) return typeResult;
294
+ }
295
+
296
+ // Validate dimensions (skip for SVG files)
297
+ if (options.requiredDimensions && !isSvgFile(file)) {
298
+ const dimensionResult = await validateImageDimensions(
299
+ file,
300
+ options.requiredDimensions
301
+ );
302
+ if (!dimensionResult.isValid) return dimensionResult;
303
+ }
304
+
305
+ return { isValid: true };
306
+ };
@@ -0,0 +1,57 @@
1
+ import { TractStackAPI } from '../api';
2
+ import { convertToBackendFormat } from './menuHelpers';
3
+ import type { MenuNode, MenuNodeState } from '@/types/tractstack';
4
+
5
+ /**
6
+ * Save an existing menu
7
+ */
8
+ export async function saveMenu(
9
+ tenantId: string,
10
+ menuState: MenuNodeState
11
+ ): Promise<MenuNode> {
12
+ const api = new TractStackAPI(tenantId);
13
+ const menuData = convertToBackendFormat(menuState);
14
+
15
+ const response = await api.put(
16
+ `/api/v1/nodes/menus/${menuState.id}`,
17
+ menuData
18
+ );
19
+ return response.data as MenuNode;
20
+ }
21
+
22
+ /**
23
+ * Create a new menu
24
+ */
25
+ export async function createMenu(
26
+ tenantId: string,
27
+ menuState: MenuNodeState
28
+ ): Promise<MenuNode> {
29
+ const api = new TractStackAPI(tenantId);
30
+ const menuData = convertToBackendFormat(menuState);
31
+
32
+ const response = await api.post('/api/v1/nodes/menus/create', menuData);
33
+ return response.data as MenuNode;
34
+ }
35
+
36
+ /**
37
+ * Delete a menu
38
+ */
39
+ export async function deleteMenu(
40
+ tenantId: string,
41
+ menuId: string
42
+ ): Promise<void> {
43
+ const api = new TractStackAPI(tenantId);
44
+ await api.request(`/api/v1/nodes/menus/${menuId}`, { method: 'DELETE' });
45
+ }
46
+
47
+ /**
48
+ * Get a menu by ID
49
+ */
50
+ export async function getMenuById(
51
+ tenantId: string,
52
+ menuId: string
53
+ ): Promise<MenuNode> {
54
+ const api = new TractStackAPI(tenantId);
55
+ const response = await api.get(`/api/v1/nodes/menus/${menuId}`);
56
+ return response.data as MenuNode;
57
+ }
@@ -0,0 +1,156 @@
1
+ import type { MenuNode, MenuNodeState, FieldErrors } from '@/types/tractstack';
2
+
3
+ /**
4
+ * Convert backend MenuNode to frontend MenuNodeState
5
+ */
6
+ export function convertToLocalState(menuNode: MenuNode): MenuNodeState {
7
+ return {
8
+ id: menuNode.id,
9
+ title: menuNode.title,
10
+ theme: menuNode.theme,
11
+ menuLinks: menuNode.optionsPayload.map((link) => ({
12
+ name: link.name,
13
+ description: link.description,
14
+ featured: link.featured,
15
+ actionLisp: link.actionLisp,
16
+ })),
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Convert frontend MenuNodeState to backend MenuNode format
22
+ */
23
+ export function convertToBackendFormat(state: MenuNodeState): MenuNode {
24
+ return {
25
+ id: state.id,
26
+ title: state.title,
27
+ theme: state.theme,
28
+ optionsPayload: state.menuLinks.map((link) => ({
29
+ name: link.name,
30
+ description: link.description,
31
+ featured: link.featured,
32
+ actionLisp: link.actionLisp,
33
+ })),
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Validate menu node state
39
+ */
40
+ export function validateMenuNode(state: MenuNodeState): FieldErrors {
41
+ const errors: FieldErrors = {};
42
+
43
+ // Validate title
44
+ if (!state.title?.trim()) {
45
+ errors.title = 'Title is required';
46
+ }
47
+
48
+ // Validate theme
49
+ if (!state.theme?.trim()) {
50
+ errors.theme = 'Theme is required';
51
+ }
52
+
53
+ // Validate menu links
54
+ if (!state.menuLinks || state.menuLinks.length === 0) {
55
+ errors.menuLinks = 'At least one menu link is required';
56
+ } else {
57
+ state.menuLinks.forEach((link, index) => {
58
+ if (!link.name?.trim()) {
59
+ errors[`menuLinks.${index}.name`] = 'Link name is required';
60
+ }
61
+ if (!link.actionLisp?.trim()) {
62
+ errors[`menuLinks.${index}.actionLisp`] = 'Action is required';
63
+ } else {
64
+ // Basic ActionLisp validation
65
+ if (!link.actionLisp.startsWith('(goto ')) {
66
+ errors[`menuLinks.${index}.actionLisp`] =
67
+ 'Action must start with "(goto "';
68
+ }
69
+ if (!link.actionLisp.endsWith('))')) {
70
+ errors[`menuLinks.${index}.actionLisp`] = 'Action must end with "))"';
71
+ }
72
+ }
73
+ });
74
+ }
75
+
76
+ return errors;
77
+ }
78
+
79
+ /**
80
+ * State interceptor for form state management
81
+ */
82
+ export function menuStateIntercept(
83
+ state: MenuNodeState,
84
+ field: keyof MenuNodeState,
85
+ value: any
86
+ ): MenuNodeState {
87
+ const newState = { ...state };
88
+
89
+ switch (field) {
90
+ case 'title':
91
+ newState.title = value || '';
92
+ break;
93
+ case 'theme':
94
+ newState.theme = value || '';
95
+ break;
96
+ case 'menuLinks':
97
+ newState.menuLinks = value || [];
98
+ break;
99
+ default:
100
+ (newState as any)[field] = value;
101
+ }
102
+
103
+ return newState;
104
+ }
105
+
106
+ /**
107
+ * Add a new menu link to the state
108
+ */
109
+ export function addMenuLink(state: MenuNodeState): MenuNodeState {
110
+ return {
111
+ ...state,
112
+ menuLinks: [
113
+ ...state.menuLinks,
114
+ {
115
+ name: '',
116
+ description: '',
117
+ featured: true,
118
+ actionLisp: '',
119
+ },
120
+ ],
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Remove a menu link from the state
126
+ */
127
+ export function removeMenuLink(
128
+ state: MenuNodeState,
129
+ index: number
130
+ ): MenuNodeState {
131
+ return {
132
+ ...state,
133
+ menuLinks: state.menuLinks.filter((_, i) => i !== index),
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Update a specific menu link in the state
139
+ */
140
+ export function updateMenuLink(
141
+ state: MenuNodeState,
142
+ index: number,
143
+ field: string,
144
+ value: any
145
+ ): MenuNodeState {
146
+ const newMenuLinks = [...state.menuLinks];
147
+ newMenuLinks[index] = {
148
+ ...newMenuLinks[index],
149
+ [field]: value,
150
+ };
151
+
152
+ return {
153
+ ...state,
154
+ menuLinks: newMenuLinks,
155
+ };
156
+ }
@@ -0,0 +1,158 @@
1
+ import { TractStackAPI } from '../api';
2
+ import { convertToLocalState, convertToBackendFormat } from './resourceHelpers';
3
+ import type { ResourceConfig, ResourceState } from '@/types/tractstack';
4
+
5
+ /**
6
+ * Save resource - handles both create and update operations
7
+ */
8
+ export async function saveResource(
9
+ tenantId: string,
10
+ resource: Partial<ResourceConfig> & { id?: string }
11
+ ): Promise<ResourceConfig> {
12
+ const api = new TractStackAPI(tenantId);
13
+ try {
14
+ const isCreate = !resource.id || resource.id === '';
15
+
16
+ let response;
17
+
18
+ if (isCreate) {
19
+ // Create new resource
20
+ response = await api.post<ResourceConfig>(
21
+ '/api/v1/nodes/resources/create',
22
+ resource
23
+ );
24
+ } else {
25
+ // Update existing resource
26
+ response = await api.put<ResourceConfig>(
27
+ `/api/v1/nodes/resources/${resource.id}`,
28
+ resource
29
+ );
30
+ }
31
+
32
+ if (!response.success || !response.data) {
33
+ throw new Error(response.error || 'Failed to save resource');
34
+ }
35
+
36
+ return response.data;
37
+ } catch (error) {
38
+ console.error('saveResource error:', error);
39
+
40
+ if (error instanceof Error) {
41
+ // Check if it's a JSON parsing error (from the error message)
42
+ if (
43
+ error.message.includes('Unexpected non-whitespace character after JSON')
44
+ ) {
45
+ throw new Error(
46
+ 'Server returned invalid JSON response. Please check the backend logs.'
47
+ );
48
+ }
49
+ throw error;
50
+ }
51
+
52
+ throw new Error('Failed to save resource');
53
+ }
54
+ }
55
+
56
+ export async function createResource(
57
+ tenantId: string,
58
+ resource: Omit<ResourceConfig, 'id'>
59
+ ): Promise<ResourceConfig> {
60
+ const api = new TractStackAPI(tenantId);
61
+ const response = await api.post('/api/v1/nodes/resources/create', resource);
62
+ if (!response.success) {
63
+ throw new Error(response.error || 'Failed to create resource');
64
+ }
65
+ return response.data;
66
+ }
67
+
68
+ export async function getResource(
69
+ tenantId: string,
70
+ id: string
71
+ ): Promise<ResourceConfig> {
72
+ const api = new TractStackAPI(tenantId);
73
+ const response = await api.get(`/api/v1/nodes/resources/${id}`);
74
+ if (!response.success) {
75
+ throw new Error(response.error || 'Failed to get resource');
76
+ }
77
+ return response.data;
78
+ }
79
+
80
+ export async function getResourceBySlug(
81
+ tenantId: string,
82
+ slug: string
83
+ ): Promise<ResourceConfig> {
84
+ const api = new TractStackAPI(tenantId);
85
+ const response = await api.get(`/api/v1/nodes/resources/slug/${slug}`);
86
+ if (!response.success) {
87
+ throw new Error(response.error || 'Failed to get resource by slug');
88
+ }
89
+ return response.data;
90
+ }
91
+
92
+ export async function deleteResource(
93
+ tenantId: string,
94
+ id: string
95
+ ): Promise<void> {
96
+ const api = new TractStackAPI(tenantId);
97
+ const response = await api.request(`/api/v1/nodes/resources/${id}`, {
98
+ method: 'DELETE',
99
+ });
100
+ if (!response.success) {
101
+ throw new Error(response.error || 'Failed to delete resource');
102
+ }
103
+ }
104
+
105
+ export async function getAllResourceIds(tenantId: string): Promise<string[]> {
106
+ const api = new TractStackAPI(tenantId);
107
+ const response = await api.get('/api/v1/nodes/resources');
108
+ if (!response.success) {
109
+ throw new Error(response.error || 'Failed to get resource IDs');
110
+ }
111
+ return response.data;
112
+ }
113
+
114
+ export async function getResourcesByIds(
115
+ tenantId: string,
116
+ ids: string[]
117
+ ): Promise<ResourceConfig[]> {
118
+ const api = new TractStackAPI(tenantId);
119
+ const response = await api.post('/api/v1/nodes/resources', { ids });
120
+ if (!response.success) {
121
+ throw new Error(response.error || 'Failed to get resources by IDs');
122
+ }
123
+ return response.data;
124
+ }
125
+
126
+ export async function getResourcesByCategory(
127
+ tenantId: string,
128
+ categorySlug: string
129
+ ): Promise<ResourceConfig[]> {
130
+ const allIds = await getAllResourceIds(tenantId);
131
+ const allResources = await getResourcesByIds(tenantId, allIds);
132
+ return allResources.filter(
133
+ (resource) => resource.categorySlug === categorySlug
134
+ );
135
+ }
136
+
137
+ export async function saveResourceWithStateUpdate(
138
+ tenantId: string,
139
+ currentState: ResourceState
140
+ ): Promise<ResourceState> {
141
+ // Convert to backend format
142
+ const backendFormat = convertToBackendFormat(currentState);
143
+
144
+ // Determine if this is a create operation
145
+ const isCreate = !currentState.id || currentState.id === '';
146
+
147
+ if (isCreate) {
148
+ // For create, remove id and call createResource
149
+ const { id, ...createData } = backendFormat;
150
+ const createdResource = await createResource(tenantId, createData);
151
+ return convertToLocalState(createdResource);
152
+ } else {
153
+ // For update, send the FULL payload (not just changed fields)
154
+ // This is required for nodes unlike brand/advanced config
155
+ const updatedResource = await saveResource(tenantId, backendFormat);
156
+ return convertToLocalState(updatedResource);
157
+ }
158
+ }