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.
- package/LICENSE +110 -0
- package/README.md +56 -0
- package/astro.d.ts +64 -0
- package/bin/create-tractstack.js +483 -0
- package/dist/config.js +80 -0
- package/dist/index.js +2129 -0
- package/package.json +89 -0
- package/templates/artpacks/kCz/captainBreakfast_1080px.webp +0 -0
- package/templates/artpacks/kCz/captainBreakfast_1920px.webp +0 -0
- package/templates/artpacks/kCz/captainBreakfast_600px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/cleanDrips_600px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_1080px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_1920px.webp +0 -0
- package/templates/artpacks/kCz/crispwaves_600px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_1080px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_1920px.webp +0 -0
- package/templates/artpacks/kCz/dragonSkin_600px.webp +0 -0
- package/templates/artpacks/kCz/dragon_1080px.webp +0 -0
- package/templates/artpacks/kCz/dragon_1920px.webp +0 -0
- package/templates/artpacks/kCz/dragon_600px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_1080px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_1920px.webp +0 -0
- package/templates/artpacks/kCz/nightcity_600px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_1080px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_1920px.webp +0 -0
- package/templates/artpacks/kCz/pattern1_600px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_1080px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_1920px.webp +0 -0
- package/templates/artpacks/kCz/pattern2_600px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/skindrips_600px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_1080px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_1920px.webp +0 -0
- package/templates/artpacks/kCz/slimetime_600px.webp +0 -0
- package/templates/artpacks/kCz/snake_1080px.webp +0 -0
- package/templates/artpacks/kCz/snake_1920px.webp +0 -0
- package/templates/artpacks/kCz/snake_600px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_1080px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_1920px.webp +0 -0
- package/templates/artpacks/kCz/toxicshock_600px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_1080px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_1920px.webp +0 -0
- package/templates/artpacks/kCz/tractstack_600px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/tripdrips_600px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_1080px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_1920px.webp +0 -0
- package/templates/artpacks/kCz/wavedrips_600px.webp +0 -0
- package/templates/artpacks/t8k/beach_1080px.webp +0 -0
- package/templates/artpacks/t8k/beach_1920px.webp +0 -0
- package/templates/artpacks/t8k/beach_600px.webp +0 -0
- package/templates/artpacks/t8k/blast_1080px.webp +0 -0
- package/templates/artpacks/t8k/blast_1920px.webp +0 -0
- package/templates/artpacks/t8k/blast_600px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_1080px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_1920px.webp +0 -0
- package/templates/artpacks/t8k/bokeh_600px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_1080px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_1920px.webp +0 -0
- package/templates/artpacks/t8k/cartoon_600px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_1080px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_1920px.webp +0 -0
- package/templates/artpacks/t8k/darkeggshell_600px.webp +0 -0
- package/templates/artpacks/t8k/explosion_1080px.webp +0 -0
- package/templates/artpacks/t8k/explosion_1920px.webp +0 -0
- package/templates/artpacks/t8k/explosion_600px.webp +0 -0
- package/templates/artpacks/t8k/floral_1080px.webp +0 -0
- package/templates/artpacks/t8k/floral_1920px.webp +0 -0
- package/templates/artpacks/t8k/floral_600px.webp +0 -0
- package/templates/artpacks/t8k/flower_1080px.webp +0 -0
- package/templates/artpacks/t8k/flower_1920px.webp +0 -0
- package/templates/artpacks/t8k/flower_600px.webp +0 -0
- package/templates/artpacks/t8k/foliage_1080px.webp +0 -0
- package/templates/artpacks/t8k/foliage_1920px.webp +0 -0
- package/templates/artpacks/t8k/foliage_600px.webp +0 -0
- package/templates/artpacks/t8k/mist_1080px.webp +0 -0
- package/templates/artpacks/t8k/mist_1920px.webp +0 -0
- package/templates/artpacks/t8k/mist_600px.webp +0 -0
- package/templates/artpacks/t8k/portal_1080px.webp +0 -0
- package/templates/artpacks/t8k/portal_1920px.webp +0 -0
- package/templates/artpacks/t8k/portal_600px.webp +0 -0
- package/templates/artpacks/t8k/storytime_1080px.webp +0 -0
- package/templates/artpacks/t8k/storytime_1920px.webp +0 -0
- package/templates/artpacks/t8k/storytime_600px.webp +0 -0
- package/templates/artpacks/t8k/tacky_1080px.webp +0 -0
- package/templates/artpacks/t8k/tacky_1920px.webp +0 -0
- package/templates/artpacks/t8k/tacky_600px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_1080px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_1920px.webp +0 -0
- package/templates/artpacks/t8k/wallpaper_600px.webp +0 -0
- package/templates/brand/favicon.ico +0 -0
- package/templates/brand/logo.svg +19 -0
- package/templates/brand/static.jpg +0 -0
- package/templates/brand/wordmark.svg +4 -0
- package/templates/css/custom.css +51 -0
- package/templates/css/frontend.css +3519 -0
- package/templates/css/storykeep.css +92872 -0
- package/templates/custom/minimal/CodeHook.astro +53 -0
- package/templates/custom/minimal/CustomRoutes.astro +46 -0
- package/templates/custom/with-examples/CodeHook.astro +49 -0
- package/templates/custom/with-examples/CustomHero.astro +13 -0
- package/templates/custom/with-examples/CustomRoutes.astro +39 -0
- package/templates/custom/with-examples/pages/Collections.astro +110 -0
- package/templates/env.example +8 -0
- package/templates/fonts/Inter-Black.woff2 +0 -0
- package/templates/fonts/Inter-Bold.woff2 +0 -0
- package/templates/fonts/Inter-Regular.woff2 +0 -0
- package/templates/icons/h2.svg +1 -0
- package/templates/icons/h3.svg +1 -0
- package/templates/icons/h4.svg +1 -0
- package/templates/icons/h5.svg +1 -0
- package/templates/icons/image.svg +7 -0
- package/templates/icons/text.svg +6 -0
- package/templates/socials/codepen.svg +1 -0
- package/templates/socials/discord.svg +1 -0
- package/templates/socials/facebook.svg +1 -0
- package/templates/socials/github.svg +1 -0
- package/templates/socials/instagram.svg +1 -0
- package/templates/socials/linkedin.svg +1 -0
- package/templates/socials/mail.svg +1 -0
- package/templates/socials/rumble.svg +1 -0
- package/templates/socials/tiktok.svg +1 -0
- package/templates/socials/twitch.svg +1 -0
- package/templates/socials/twitter.svg +1 -0
- package/templates/socials/x.svg +1 -0
- package/templates/socials/youtube.svg +1 -0
- package/templates/src/client/analytics-events.ts +213 -0
- package/templates/src/client/belief-events.ts +205 -0
- package/templates/src/client/sse.ts +667 -0
- package/templates/src/components/Footer.astro +246 -0
- package/templates/src/components/Fragment.astro +70 -0
- package/templates/src/components/Header.astro +458 -0
- package/templates/src/components/Menu.tsx +196 -0
- package/templates/src/components/codehooks/BunnyVideoSetup.tsx +692 -0
- package/templates/src/components/codehooks/BunnyVideoWrapper.astro +78 -0
- package/templates/src/components/codehooks/EpinetDurationSelector.tsx +1020 -0
- package/templates/src/components/codehooks/EpinetTableView.tsx +594 -0
- package/templates/src/components/codehooks/EpinetWrapper.tsx +424 -0
- package/templates/src/components/codehooks/FeaturedContent.astro +273 -0
- package/templates/src/components/codehooks/FeaturedContentSetup.tsx +738 -0
- package/templates/src/components/codehooks/ListContent.astro +460 -0
- package/templates/src/components/codehooks/ListContentSetup.tsx +649 -0
- package/templates/src/components/codehooks/SankeyDiagram.tsx +359 -0
- package/templates/src/components/compositor/Compositor.tsx +144 -0
- package/templates/src/components/compositor/Node.tsx +415 -0
- package/templates/src/components/compositor/NodeWithGuid.tsx +25 -0
- package/templates/src/components/compositor/PanelVisibilityWrapper.tsx +87 -0
- package/templates/src/components/compositor/elements/Belief.tsx +148 -0
- package/templates/src/components/compositor/elements/BgImage.tsx +118 -0
- package/templates/src/components/compositor/elements/BgVisualBreak.tsx +102 -0
- package/templates/src/components/compositor/elements/BunnyVideo.tsx +63 -0
- package/templates/src/components/compositor/elements/IdentifyAs.tsx +66 -0
- package/templates/src/components/compositor/elements/PlayButton.tsx +19 -0
- package/templates/src/components/compositor/elements/SignUp.tsx +179 -0
- package/templates/src/components/compositor/elements/Svg.tsx +33 -0
- package/templates/src/components/compositor/elements/ToggleBelief.tsx +36 -0
- package/templates/src/components/compositor/elements/YouTubeWrapper.tsx +33 -0
- package/templates/src/components/compositor/nodes/BgPaneWrapper.tsx +35 -0
- package/templates/src/components/compositor/nodes/GhostInsertBlock.tsx +189 -0
- package/templates/src/components/compositor/nodes/Markdown.tsx +179 -0
- package/templates/src/components/compositor/nodes/Pane.tsx +277 -0
- package/templates/src/components/compositor/nodes/Pane_eraser.tsx +69 -0
- package/templates/src/components/compositor/nodes/Pane_layout.tsx +77 -0
- package/templates/src/components/compositor/nodes/RenderChildren.tsx +19 -0
- package/templates/src/components/compositor/nodes/StoryFragment.tsx +35 -0
- package/templates/src/components/compositor/nodes/TagElement.tsx +14 -0
- package/templates/src/components/compositor/nodes/Widget.tsx +115 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeA.tsx +4 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeA_eraser.tsx +26 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeAnchorComponent.tsx +248 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag.tsx +684 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_eraser.tsx +62 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_insert.tsx +120 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeBasicTag_settings.tsx +62 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeButton.tsx +5 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeButton_eraser.tsx +26 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeImg.tsx +28 -0
- package/templates/src/components/compositor/nodes/tagElements/NodeText.tsx +18 -0
- package/templates/src/components/compositor/nodes/tagElements/TabIndicator.tsx +51 -0
- package/templates/src/components/compositor/preview/FeaturedContentPreview.tsx +128 -0
- package/templates/src/components/compositor/preview/ListContentPreview.tsx +213 -0
- package/templates/src/components/compositor/preview/OgImagePreview.tsx +223 -0
- package/templates/src/components/compositor/preview/PaneSnapshotGenerator.tsx +199 -0
- package/templates/src/components/compositor/preview/PanesPreviewGenerator.tsx +123 -0
- package/templates/src/components/compositor/preview/VisualBreakPreview.tsx +154 -0
- package/templates/src/components/edit/Header.tsx +181 -0
- package/templates/src/components/edit/PanelSwitch.tsx +446 -0
- package/templates/src/components/edit/SettingsPanel.tsx +70 -0
- package/templates/src/components/edit/ToolBar.tsx +101 -0
- package/templates/src/components/edit/ToolMode.tsx +121 -0
- package/templates/src/components/edit/context/ContextPaneConfig.tsx +91 -0
- package/templates/src/components/edit/context/ContextPaneConfig_slug.tsx +174 -0
- package/templates/src/components/edit/context/ContextPaneConfig_title.tsx +186 -0
- package/templates/src/components/edit/pane/AddPanePanel.tsx +136 -0
- package/templates/src/components/edit/pane/AddPanePanel_break.tsx +470 -0
- package/templates/src/components/edit/pane/AddPanePanel_codehook.tsx +264 -0
- package/templates/src/components/edit/pane/AddPanePanel_new.tsx +623 -0
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy.tsx +107 -0
- package/templates/src/components/edit/pane/AddPanePanel_newAICopy_modal.tsx +217 -0
- package/templates/src/components/edit/pane/AddPanePanel_newCopyMode.tsx +109 -0
- package/templates/src/components/edit/pane/AddPanePanel_newCustomCopy.tsx +39 -0
- package/templates/src/components/edit/pane/AddPanePanel_reuse.tsx +445 -0
- package/templates/src/components/edit/pane/ConfigPanePanel.tsx +245 -0
- package/templates/src/components/edit/pane/PageGen.tsx +485 -0
- package/templates/src/components/edit/pane/PageGenSelector.tsx +238 -0
- package/templates/src/components/edit/pane/PageGenSpecial.tsx +362 -0
- package/templates/src/components/edit/pane/PageGen_preview.tsx +495 -0
- package/templates/src/components/edit/pane/PanePanel_impression.tsx +258 -0
- package/templates/src/components/edit/pane/PanePanel_path.tsx +268 -0
- package/templates/src/components/edit/pane/PanePanel_slug.tsx +219 -0
- package/templates/src/components/edit/pane/PanePanel_title.tsx +142 -0
- package/templates/src/components/edit/panels/StyleBreakPanel.tsx +182 -0
- package/templates/src/components/edit/panels/StyleCodeHookPanel.tsx +439 -0
- package/templates/src/components/edit/panels/StyleElementPanel.tsx +177 -0
- package/templates/src/components/edit/panels/StyleElementPanel_add.tsx +349 -0
- package/templates/src/components/edit/panels/StyleElementPanel_remove.tsx +159 -0
- package/templates/src/components/edit/panels/StyleElementPanel_update.tsx +320 -0
- package/templates/src/components/edit/panels/StyleImagePanel.tsx +460 -0
- package/templates/src/components/edit/panels/StyleImagePanel_add.tsx +296 -0
- package/templates/src/components/edit/panels/StyleImagePanel_remove.tsx +153 -0
- package/templates/src/components/edit/panels/StyleImagePanel_update.tsx +312 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel.tsx +273 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_add.tsx +301 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_remove.tsx +132 -0
- package/templates/src/components/edit/panels/StyleLiElementPanel_update.tsx +313 -0
- package/templates/src/components/edit/panels/StyleLinkPanel.tsx +346 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_add.tsx +265 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_config.tsx +240 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_remove.tsx +94 -0
- package/templates/src/components/edit/panels/StyleLinkPanel_update.tsx +110 -0
- package/templates/src/components/edit/panels/StyleParentPanel.tsx +263 -0
- package/templates/src/components/edit/panels/StyleParentPanel_add.tsx +275 -0
- package/templates/src/components/edit/panels/StyleParentPanel_deleteLayer.tsx +112 -0
- package/templates/src/components/edit/panels/StyleParentPanel_remove.tsx +87 -0
- package/templates/src/components/edit/panels/StyleParentPanel_update.tsx +141 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel.tsx +428 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_add.tsx +292 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_config.tsx +190 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_remove.tsx +152 -0
- package/templates/src/components/edit/panels/StyleWidgetPanel_update.tsx +308 -0
- package/templates/src/components/edit/state/SaveModal.tsx +811 -0
- package/templates/src/components/edit/state/StylesMemory.tsx +310 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentConfigPanel.tsx +289 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_menu.tsx +320 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_og.tsx +888 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_slug.tsx +269 -0
- package/templates/src/components/edit/storyfragment/StoryFragmentPanel_title.tsx +190 -0
- package/templates/src/components/edit/widgets/BeliefWidget.tsx +183 -0
- package/templates/src/components/edit/widgets/BunnyWidget.tsx +134 -0
- package/templates/src/components/edit/widgets/IdentifyAsWidget.tsx +193 -0
- package/templates/src/components/edit/widgets/SignupWidget.tsx +177 -0
- package/templates/src/components/edit/widgets/ToggleWidget.tsx +152 -0
- package/templates/src/components/edit/widgets/YouTubeWidget.tsx +65 -0
- package/templates/src/components/fields/ActionBuilderTimeSelector.tsx +353 -0
- package/templates/src/components/fields/ArtpackImage.tsx +480 -0
- package/templates/src/components/fields/BackgroundImage.tsx +530 -0
- package/templates/src/components/fields/BackgroundImageWrapper.tsx +192 -0
- package/templates/src/components/fields/BooleanParam.tsx +67 -0
- package/templates/src/components/fields/BunnyMomentSelector.tsx +56 -0
- package/templates/src/components/fields/ColorPickerCombo.tsx +284 -0
- package/templates/src/components/fields/ImageUpload.tsx +405 -0
- package/templates/src/components/fields/MultiParam.tsx +75 -0
- package/templates/src/components/fields/PaneBreakCollectionSelector.tsx +97 -0
- package/templates/src/components/fields/PaneBreakShapeSelector.tsx +134 -0
- package/templates/src/components/fields/SelectedTailwindClass.tsx +44 -0
- package/templates/src/components/fields/SingleParam.tsx +73 -0
- package/templates/src/components/fields/ViewportComboBox.tsx +252 -0
- package/templates/src/components/form/ActionBuilderField.tsx +282 -0
- package/templates/src/components/form/ActionBuilderSlugSelector.tsx +182 -0
- package/templates/src/components/form/BooleanToggle.tsx +94 -0
- package/templates/src/components/form/ColorPicker.tsx +153 -0
- package/templates/src/components/form/DateTimeInput.tsx +638 -0
- package/templates/src/components/form/EnumSelect.tsx +88 -0
- package/templates/src/components/form/FileUpload.tsx +465 -0
- package/templates/src/components/form/MagicPathBuilder.tsx +546 -0
- package/templates/src/components/form/NumberInput.tsx +101 -0
- package/templates/src/components/form/ParagraphArrayInput.tsx +207 -0
- package/templates/src/components/form/StringArrayInput.tsx +163 -0
- package/templates/src/components/form/StringInput.tsx +88 -0
- package/templates/src/components/form/UnsavedChangesBar.tsx +295 -0
- package/templates/src/components/form/advanced/APIConfigSection.tsx +69 -0
- package/templates/src/components/form/advanced/AuthConfigSection.tsx +97 -0
- package/templates/src/components/form/brand/BrandAssetsSection.tsx +93 -0
- package/templates/src/components/form/brand/BrandColorsSection.tsx +201 -0
- package/templates/src/components/form/brand/SEOSection.tsx +101 -0
- package/templates/src/components/form/brand/SiteConfigSection.tsx +61 -0
- package/templates/src/components/form/brand/SocialLinksSection.tsx +393 -0
- package/templates/src/components/profile/ProfileConsent.tsx +65 -0
- package/templates/src/components/profile/ProfileCreate.tsx +462 -0
- package/templates/src/components/profile/ProfileEdit.tsx +409 -0
- package/templates/src/components/profile/ProfileSwitch.tsx +255 -0
- package/templates/src/components/profile/ProfileUnlock.tsx +221 -0
- package/templates/src/components/storykeep/Dashboard.tsx +160 -0
- package/templates/src/components/storykeep/Dashboard_Activity.tsx +56 -0
- package/templates/src/components/storykeep/Dashboard_Advanced.tsx +165 -0
- package/templates/src/components/storykeep/Dashboard_Analytics.tsx +451 -0
- package/templates/src/components/storykeep/Dashboard_Branding.tsx +95 -0
- package/templates/src/components/storykeep/Dashboard_Content.tsx +191 -0
- package/templates/src/components/storykeep/controls/UsageCell.tsx +71 -0
- package/templates/src/components/storykeep/controls/content/BeliefForm.tsx +378 -0
- package/templates/src/components/storykeep/controls/content/BeliefTable.tsx +329 -0
- package/templates/src/components/storykeep/controls/content/ContentBrowser.tsx +385 -0
- package/templates/src/components/storykeep/controls/content/ContentSummary.tsx +149 -0
- package/templates/src/components/storykeep/controls/content/KnownResourceForm.tsx +397 -0
- package/templates/src/components/storykeep/controls/content/KnownResourceTable.tsx +260 -0
- package/templates/src/components/storykeep/controls/content/ManageContent.tsx +439 -0
- package/templates/src/components/storykeep/controls/content/MenuForm.tsx +239 -0
- package/templates/src/components/storykeep/controls/content/MenuTable.tsx +332 -0
- package/templates/src/components/storykeep/controls/content/ResourceBulkIngest.tsx +724 -0
- package/templates/src/components/storykeep/controls/content/ResourceForm.tsx +355 -0
- package/templates/src/components/storykeep/controls/content/ResourceTable.tsx +222 -0
- package/templates/src/components/storykeep/controls/content/StoryFragmentTable.tsx +482 -0
- package/templates/src/components/storykeep/state/BrandingWrapper.tsx +42 -0
- package/templates/src/components/storykeep/state/FetchAnalytics.tsx +350 -0
- package/templates/src/components/storykeep/widgets/ResponsiveLine.tsx +319 -0
- package/templates/src/components/storykeep/widgets/Wizard.tsx +278 -0
- package/templates/src/components/tenant/RegistrationForm.tsx +447 -0
- package/templates/src/components/widgets/BunnyVideoHero.astro +775 -0
- package/templates/src/components/widgets/Impression.tsx +102 -0
- package/templates/src/components/widgets/ImpressionWrapper.tsx +214 -0
- package/templates/src/constants/beliefs.ts +61 -0
- package/templates/src/constants/brandThemes.ts +133 -0
- package/templates/src/constants/prompts.json +55 -0
- package/templates/src/constants/shapes.ts +556 -0
- package/templates/src/constants/stopWords.ts +116 -0
- package/templates/src/constants/tailwindColors.json +344 -0
- package/templates/src/constants.ts +274 -0
- package/templates/src/hooks/useFormState.ts +203 -0
- package/templates/src/layouts/Layout.astro +290 -0
- package/templates/src/lib/session.ts +126 -0
- package/templates/src/lib/storyData.ts +56 -0
- package/templates/src/middleware.ts +52 -0
- package/templates/src/pages/404.astro +54 -0
- package/templates/src/pages/[...slug]/edit.astro +216 -0
- package/templates/src/pages/[...slug].astro +148 -0
- package/templates/src/pages/api/auth/decode.ts +101 -0
- package/templates/src/pages/api/auth/login.ts +122 -0
- package/templates/src/pages/api/auth/logout.ts +37 -0
- package/templates/src/pages/api/auth/profile.ts +76 -0
- package/templates/src/pages/api/orphan-analysis.ts +106 -0
- package/templates/src/pages/api/tailwind.ts +116 -0
- package/templates/src/pages/collections/[param1].astro +65 -0
- package/templates/src/pages/context/[...contextSlug]/edit.astro +207 -0
- package/templates/src/pages/context/[...contextSlug].astro +161 -0
- package/templates/src/pages/llms.txt.ts +122 -0
- package/templates/src/pages/maint.astro +183 -0
- package/templates/src/pages/media/[...slug].astro +67 -0
- package/templates/src/pages/robots.txt.ts +36 -0
- package/templates/src/pages/sandbox/activate.astro +258 -0
- package/templates/src/pages/sandbox/register.astro +44 -0
- package/templates/src/pages/sandbox/success.astro +179 -0
- package/templates/src/pages/sitemap.xml.ts +119 -0
- package/templates/src/pages/storykeep/advanced.astro +69 -0
- package/templates/src/pages/storykeep/branding.astro +57 -0
- package/templates/src/pages/storykeep/content.astro +71 -0
- package/templates/src/pages/storykeep/init.astro +36 -0
- package/templates/src/pages/storykeep/login.astro +266 -0
- package/templates/src/pages/storykeep/logout.astro +84 -0
- package/templates/src/pages/storykeep/profile.astro +98 -0
- package/templates/src/pages/storykeep.astro +81 -0
- package/templates/src/stores/analytics.ts +171 -0
- package/templates/src/stores/backend.ts +16 -0
- package/templates/src/stores/navigation.ts +149 -0
- package/templates/src/stores/nodes.ts +2390 -0
- package/templates/src/stores/nodesHistory.ts +85 -0
- package/templates/src/stores/notificationSystem.ts +41 -0
- package/templates/src/stores/orphanAnalysis.ts +409 -0
- package/templates/src/stores/storykeep.ts +247 -0
- package/templates/src/types/astro.ts +86 -0
- package/templates/src/types/compositorTypes.ts +456 -0
- package/templates/src/types/formTypes.ts +281 -0
- package/templates/src/types/multiTenant.ts +77 -0
- package/templates/src/types/nodeProps.ts +66 -0
- package/templates/src/types/tractstack.ts +445 -0
- package/templates/src/utils/aai/getTitleSlug.ts +72 -0
- package/templates/src/utils/actions/actionButton.ts +101 -0
- package/templates/src/utils/actions/lispLexer.ts +57 -0
- package/templates/src/utils/actions/preParse_Action.ts +85 -0
- package/templates/src/utils/actions/preParse_Bunny.ts +50 -0
- package/templates/src/utils/actions/preParse_Clicked.ts +87 -0
- package/templates/src/utils/actions/preParse_Impression.ts +71 -0
- package/templates/src/utils/api/advancedConfig.ts +66 -0
- package/templates/src/utils/api/advancedHelpers.ts +134 -0
- package/templates/src/utils/api/beliefConfig.ts +87 -0
- package/templates/src/utils/api/beliefHelpers.ts +196 -0
- package/templates/src/utils/api/brandConfig.ts +126 -0
- package/templates/src/utils/api/brandHelpers.ts +155 -0
- package/templates/src/utils/api/fileHelpers.ts +306 -0
- package/templates/src/utils/api/menuConfig.ts +57 -0
- package/templates/src/utils/api/menuHelpers.ts +156 -0
- package/templates/src/utils/api/resourceConfig.ts +158 -0
- package/templates/src/utils/api/resourceHelpers.ts +72 -0
- package/templates/src/utils/api/tenantConfig.ts +97 -0
- package/templates/src/utils/api/tenantHelpers.ts +172 -0
- package/templates/src/utils/api.ts +183 -0
- package/templates/src/utils/auth.ts +150 -0
- package/templates/src/utils/backend.ts +243 -0
- package/templates/src/utils/compositor/TemplateMarkdowns.ts +118 -0
- package/templates/src/utils/compositor/TemplateNodes.ts +138 -0
- package/templates/src/utils/compositor/TemplatePanes.ts +100 -0
- package/templates/src/utils/compositor/allowInsert.ts +100 -0
- package/templates/src/utils/compositor/domHelpers.ts +37 -0
- package/templates/src/utils/compositor/handleClickEvent.ts +131 -0
- package/templates/src/utils/compositor/nodesHelper.ts +491 -0
- package/templates/src/utils/compositor/nodesMarkdownGenerator.ts +292 -0
- package/templates/src/utils/compositor/processMarkdown.ts +431 -0
- package/templates/src/utils/compositor/reduceNodesClassNames.ts +192 -0
- package/templates/src/utils/compositor/tailwindClasses.ts +1795 -0
- package/templates/src/utils/compositor/tailwindColors.ts +227 -0
- package/templates/src/utils/compositor/templateMarkdownStyles.ts +1265 -0
- package/templates/src/utils/compositor/typeGuards.ts +193 -0
- package/templates/src/utils/etl/extractor.ts +119 -0
- package/templates/src/utils/etl/index.ts +88 -0
- package/templates/src/utils/etl/loader.ts +36 -0
- package/templates/src/utils/etl/transformer.ts +286 -0
- package/templates/src/utils/helpers.ts +435 -0
- package/templates/src/utils/layout.ts +209 -0
- package/templates/src/utils/profileStorage.ts +306 -0
- package/templates/src/utils/useInterval.ts +27 -0
- package/templates/tailwind.config.cjs +169 -0
- package/utils/create-resolver.ts +10 -0
- package/utils/inject-files.ts +2140 -0
- package/utils/validate-config.ts +43 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ResourceConfig,
|
|
3
|
+
ResourceState,
|
|
4
|
+
FieldErrors,
|
|
5
|
+
} from '@/types/tractstack';
|
|
6
|
+
|
|
7
|
+
export function convertToLocalState(backend: ResourceConfig): ResourceState {
|
|
8
|
+
return {
|
|
9
|
+
id: backend.id || '',
|
|
10
|
+
title: backend.title || '',
|
|
11
|
+
slug: backend.slug || '',
|
|
12
|
+
categorySlug: backend.categorySlug || '',
|
|
13
|
+
oneliner: backend.oneliner || '',
|
|
14
|
+
optionsPayload: backend.optionsPayload || {},
|
|
15
|
+
actionLisp: backend.actionLisp || undefined,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function convertToBackendFormat(local: ResourceState): ResourceConfig {
|
|
20
|
+
return {
|
|
21
|
+
id: local.id,
|
|
22
|
+
title: local.title,
|
|
23
|
+
slug: local.slug,
|
|
24
|
+
categorySlug: local.categorySlug,
|
|
25
|
+
oneliner: local.oneliner,
|
|
26
|
+
optionsPayload: local.optionsPayload || {},
|
|
27
|
+
actionLisp: local.actionLisp || undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function validateResource(state: ResourceState): FieldErrors {
|
|
32
|
+
const errors: FieldErrors = {};
|
|
33
|
+
|
|
34
|
+
if (!state.title?.trim()) {
|
|
35
|
+
errors.title = 'Title is required';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!state.slug?.trim()) {
|
|
39
|
+
errors.slug = 'Slug is required';
|
|
40
|
+
} else if (!/^[a-z0-9-]+$/.test(state.slug)) {
|
|
41
|
+
errors.slug =
|
|
42
|
+
'Slug must contain only lowercase letters, numbers, and hyphens';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!state.categorySlug?.trim()) {
|
|
46
|
+
errors.categorySlug = 'Category is required';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return errors;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resourceStateIntercept(
|
|
53
|
+
updatedState: ResourceState,
|
|
54
|
+
fieldName: string
|
|
55
|
+
): ResourceState {
|
|
56
|
+
const newState = { ...updatedState };
|
|
57
|
+
|
|
58
|
+
if (fieldName === 'title' || fieldName === 'categorySlug') {
|
|
59
|
+
if (newState.title && newState.categorySlug) {
|
|
60
|
+
const titleSlug = newState.title
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
63
|
+
.replace(/\s+/g, '-')
|
|
64
|
+
.replace(/-+/g, '-')
|
|
65
|
+
.trim();
|
|
66
|
+
|
|
67
|
+
newState.slug = `${newState.categorySlug}-${titleSlug}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return newState;
|
|
72
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Tenant configuration API utilities following TractStack v2 patterns
|
|
2
|
+
import { TractStackAPI } from '../api';
|
|
3
|
+
import type {
|
|
4
|
+
TenantProvisioningData,
|
|
5
|
+
TenantCapacity,
|
|
6
|
+
TenantProvisioningResponse,
|
|
7
|
+
} from '@/types/multiTenant';
|
|
8
|
+
import type { TenantActivationRequest } from '@/types/multiTenant';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Check tenant capacity and existing tenants
|
|
12
|
+
*/
|
|
13
|
+
export async function checkTenantCapacity(
|
|
14
|
+
tenantId: string
|
|
15
|
+
): Promise<TenantCapacity> {
|
|
16
|
+
const api = new TractStackAPI(tenantId);
|
|
17
|
+
try {
|
|
18
|
+
const response = await api.get<TenantCapacity>('/api/v1/tenant/capacity');
|
|
19
|
+
|
|
20
|
+
if (!response.success || !response.data) {
|
|
21
|
+
throw new Error(response.error || 'No data received from server');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = response.data;
|
|
25
|
+
|
|
26
|
+
// Validate response structure to match backend
|
|
27
|
+
if (
|
|
28
|
+
typeof data.available !== 'boolean' ||
|
|
29
|
+
typeof data.currentTenants !== 'number' ||
|
|
30
|
+
typeof data.maxTenants !== 'number' ||
|
|
31
|
+
typeof data.availableSlots !== 'number'
|
|
32
|
+
) {
|
|
33
|
+
throw new Error('Invalid response format from server');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return data;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Failed to fetch tenant capacity:', error);
|
|
39
|
+
throw new Error('Failed to load tenant capacity information');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Provision a new tenant with reserved status
|
|
45
|
+
*/
|
|
46
|
+
export async function provisionTenant(
|
|
47
|
+
tenantId: string,
|
|
48
|
+
data: TenantProvisioningData
|
|
49
|
+
): Promise<TenantProvisioningResponse> {
|
|
50
|
+
const api = new TractStackAPI(tenantId);
|
|
51
|
+
try {
|
|
52
|
+
const response = await api.post<TenantProvisioningResponse>(
|
|
53
|
+
'/api/v1/tenant/provision',
|
|
54
|
+
data
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!response.success || !response.data) {
|
|
58
|
+
throw new Error(response.error || 'Failed to provision tenant');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return response.data;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Failed to provision tenant:', error);
|
|
64
|
+
|
|
65
|
+
if (error instanceof Error) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error('Failed to provision tenant');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Activate a tenant using the activation token
|
|
75
|
+
*/
|
|
76
|
+
export async function activateTenant(
|
|
77
|
+
tenantId: string,
|
|
78
|
+
token: string
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
const api = new TractStackAPI(tenantId);
|
|
81
|
+
try {
|
|
82
|
+
const request: TenantActivationRequest = { token };
|
|
83
|
+
const response = await api.post('/api/v1/activate-tenant', request);
|
|
84
|
+
|
|
85
|
+
if (!response.success) {
|
|
86
|
+
throw new Error(response.error || 'Failed to activate tenant');
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('Failed to activate tenant:', error);
|
|
90
|
+
|
|
91
|
+
if (error instanceof Error) {
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
throw new Error('Failed to activate tenant');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Tenant form validation and state helpers following TractStack v2 patterns
|
|
2
|
+
import type { TenantProvisioningData } from '@/types/multiTenant';
|
|
3
|
+
import type {
|
|
4
|
+
TenantRegistrationState,
|
|
5
|
+
TenantValidationErrors,
|
|
6
|
+
} from '@/types/multiTenant';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Convert tenant provisioning data to local form state
|
|
10
|
+
*/
|
|
11
|
+
export function convertToLocalState(
|
|
12
|
+
data?: TenantProvisioningData
|
|
13
|
+
): TenantRegistrationState {
|
|
14
|
+
if (!data) {
|
|
15
|
+
return {
|
|
16
|
+
tenantId: '',
|
|
17
|
+
adminPassword: '',
|
|
18
|
+
confirmPassword: '',
|
|
19
|
+
name: '',
|
|
20
|
+
email: '',
|
|
21
|
+
tursoEnabled: false,
|
|
22
|
+
tursoDatabaseURL: '',
|
|
23
|
+
tursoAuthToken: '',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
tenantId: data.tenantId,
|
|
29
|
+
adminPassword: data.adminPassword,
|
|
30
|
+
confirmPassword: '',
|
|
31
|
+
name: data.name,
|
|
32
|
+
email: data.adminEmail,
|
|
33
|
+
tursoEnabled: data.tursoEnabled,
|
|
34
|
+
tursoDatabaseURL: data.tursoDatabaseURL || '',
|
|
35
|
+
tursoAuthToken: data.tursoAuthToken || '',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert local form state to backend format
|
|
41
|
+
*/
|
|
42
|
+
export function convertToBackendFormat(
|
|
43
|
+
state: TenantRegistrationState
|
|
44
|
+
): TenantProvisioningData {
|
|
45
|
+
const data: TenantProvisioningData = {
|
|
46
|
+
tenantId: state.tenantId.trim(),
|
|
47
|
+
adminPassword: state.adminPassword.trim(),
|
|
48
|
+
name: state.name.trim(),
|
|
49
|
+
adminEmail: state.email.trim(),
|
|
50
|
+
tursoEnabled: state.tursoEnabled,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Only include Turso credentials if enabled
|
|
54
|
+
if (state.tursoEnabled) {
|
|
55
|
+
data.tursoDatabaseURL = state.tursoDatabaseURL.trim();
|
|
56
|
+
data.tursoAuthToken = state.tursoAuthToken.trim();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return data;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validate tenant registration form
|
|
64
|
+
*/
|
|
65
|
+
export function validateTenantRegistration(
|
|
66
|
+
state: TenantRegistrationState,
|
|
67
|
+
existingTenants?: string[],
|
|
68
|
+
isInitMode?: boolean
|
|
69
|
+
): TenantValidationErrors {
|
|
70
|
+
const errors: TenantValidationErrors = {};
|
|
71
|
+
|
|
72
|
+
// Skip ALL tenant ID validation in init mode
|
|
73
|
+
if (!isInitMode) {
|
|
74
|
+
const tenantId = state.tenantId.trim();
|
|
75
|
+
if (!tenantId) {
|
|
76
|
+
errors.tenantId = 'Tenant ID is required';
|
|
77
|
+
} else if (tenantId.length < 3 || tenantId.length > 12) {
|
|
78
|
+
errors.tenantId = 'Tenant ID must be 3-12 characters long';
|
|
79
|
+
} else if (tenantId !== tenantId.toLowerCase()) {
|
|
80
|
+
errors.tenantId = 'Tenant ID must be lowercase';
|
|
81
|
+
} else if (!/^[a-z0-9-]+$/.test(tenantId)) {
|
|
82
|
+
errors.tenantId =
|
|
83
|
+
'Tenant ID can only contain lowercase letters, numbers, and dashes';
|
|
84
|
+
} else if (tenantId === 'default') {
|
|
85
|
+
errors.tenantId = "'default' is a reserved tenant ID";
|
|
86
|
+
} else if (existingTenants && existingTenants.includes(tenantId)) {
|
|
87
|
+
errors.tenantId = 'This tenant ID is already taken';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Admin password validation
|
|
92
|
+
if (!state.adminPassword.trim()) {
|
|
93
|
+
errors.adminPassword = 'Admin password is required';
|
|
94
|
+
} else if (state.adminPassword.length < 8) {
|
|
95
|
+
errors.adminPassword = 'Admin password must be at least 8 characters long';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Password confirmation validation - only if main password is valid
|
|
99
|
+
if (!errors.adminPassword && state.adminPassword !== state.confirmPassword) {
|
|
100
|
+
errors.confirmPassword = 'Passwords do not match';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Name validation
|
|
104
|
+
if (!state.name.trim()) {
|
|
105
|
+
errors.name = 'Name is required';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Email validation
|
|
109
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
110
|
+
if (!state.email.trim()) {
|
|
111
|
+
errors.email = 'Email is required';
|
|
112
|
+
} else if (!emailRegex.test(state.email.trim())) {
|
|
113
|
+
errors.email = 'Please enter a valid email address';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Turso validation (if enabled)
|
|
117
|
+
if (state.tursoEnabled) {
|
|
118
|
+
if (!state.tursoDatabaseURL.trim()) {
|
|
119
|
+
errors.tursoDatabaseURL =
|
|
120
|
+
'Turso Database URL is required when Turso is enabled';
|
|
121
|
+
} else if (!state.tursoDatabaseURL.startsWith('libsql://')) {
|
|
122
|
+
errors.tursoDatabaseURL =
|
|
123
|
+
'Turso Database URL must start with "libsql://"';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!state.tursoAuthToken.trim()) {
|
|
127
|
+
errors.tursoAuthToken =
|
|
128
|
+
'Turso Auth Token is required when Turso is enabled';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return errors;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* State interceptor for cross-field logic
|
|
137
|
+
*/
|
|
138
|
+
export function tenantStateIntercept(
|
|
139
|
+
newState: TenantRegistrationState,
|
|
140
|
+
field: keyof TenantRegistrationState,
|
|
141
|
+
value: any
|
|
142
|
+
): TenantRegistrationState {
|
|
143
|
+
// Clear Turso fields when disabled
|
|
144
|
+
if (field === 'tursoEnabled' && !value) {
|
|
145
|
+
return {
|
|
146
|
+
...newState,
|
|
147
|
+
tursoEnabled: false,
|
|
148
|
+
tursoDatabaseURL: '',
|
|
149
|
+
tursoAuthToken: '',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Clear confirmation password when main password changes
|
|
154
|
+
if (field === 'adminPassword') {
|
|
155
|
+
return {
|
|
156
|
+
...newState,
|
|
157
|
+
adminPassword: value,
|
|
158
|
+
confirmPassword: '', // Clear confirmation to force re-entry
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Normalize tenant ID
|
|
163
|
+
if (field === 'tenantId' && typeof value === 'string') {
|
|
164
|
+
return {
|
|
165
|
+
...newState,
|
|
166
|
+
tenantId: value.toLowerCase().replace(/[^a-z0-9-]/g, ''),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Default behavior
|
|
171
|
+
return newState;
|
|
172
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
export interface APIResponse<T = any> {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: T;
|
|
4
|
+
error?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TractStackEvent {
|
|
9
|
+
type: 'page_view' | 'click' | 'form_submit' | 'custom';
|
|
10
|
+
target?: string;
|
|
11
|
+
data?: Record<string, any>;
|
|
12
|
+
timestamp?: number;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
userId?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getConfig() {
|
|
18
|
+
// Server-side safety check
|
|
19
|
+
if (typeof window === 'undefined') {
|
|
20
|
+
return {
|
|
21
|
+
goBackend: import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080',
|
|
22
|
+
tenantId: import.meta.env.PUBLIC_TENANTID || 'default',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
goBackend: import.meta.env.PUBLIC_GO_BACKEND || 'http://localhost:8080',
|
|
28
|
+
tenantId:
|
|
29
|
+
window.TRACTSTACK_CONFIG?.tenantId ||
|
|
30
|
+
import.meta.env.PUBLIC_TENANTID ||
|
|
31
|
+
'default',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//function getTenantFromDomain(): string {
|
|
36
|
+
// if (typeof window === 'undefined') return 'default';
|
|
37
|
+
//
|
|
38
|
+
// const hostname = window.location.hostname;
|
|
39
|
+
//
|
|
40
|
+
// //if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
|
41
|
+
// // return 'default';
|
|
42
|
+
// //}
|
|
43
|
+
//
|
|
44
|
+
// const parts = hostname.split('.');
|
|
45
|
+
// if (
|
|
46
|
+
// parts.length >= 4 &&
|
|
47
|
+
// parts[1] === 'sandbox' &&
|
|
48
|
+
// ['tractstack', 'freewebpress'].includes(parts[2]) &&
|
|
49
|
+
// parts[3] === 'com'
|
|
50
|
+
// ) {
|
|
51
|
+
// return parts[0];
|
|
52
|
+
// }
|
|
53
|
+
//
|
|
54
|
+
// return 'default';
|
|
55
|
+
//}
|
|
56
|
+
|
|
57
|
+
export class TractStackAPI {
|
|
58
|
+
private baseUrl: string;
|
|
59
|
+
private tenantId: string;
|
|
60
|
+
|
|
61
|
+
constructor(tenantId?: string) {
|
|
62
|
+
const config = getConfig();
|
|
63
|
+
this.baseUrl = config.goBackend;
|
|
64
|
+
this.tenantId = tenantId || config.tenantId;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async request<T = any>(
|
|
68
|
+
endpoint: string,
|
|
69
|
+
options: RequestInit = {}
|
|
70
|
+
): Promise<APIResponse<T>> {
|
|
71
|
+
const url = `${this.baseUrl}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`;
|
|
72
|
+
|
|
73
|
+
const defaultHeaders = {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
'X-Tenant-ID': this.tenantId,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const response = await fetch(url, {
|
|
80
|
+
...options,
|
|
81
|
+
credentials: 'include',
|
|
82
|
+
headers: {
|
|
83
|
+
...defaultHeaders,
|
|
84
|
+
...options.headers,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const data = await response.json();
|
|
89
|
+
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: data.error || `HTTP ${response.status}`,
|
|
94
|
+
data: data.data,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
data: data.data || data,
|
|
101
|
+
message: data.message,
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: error instanceof Error ? error.message : 'Network error',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async get<T = any>(endpoint: string): Promise<APIResponse<T>> {
|
|
112
|
+
return this.request<T>(endpoint, { method: 'GET' });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async post<T = any>(endpoint: string, data?: any): Promise<APIResponse<T>> {
|
|
116
|
+
return this.request<T>(endpoint, {
|
|
117
|
+
method: 'POST',
|
|
118
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async put<T = any>(endpoint: string, data?: any): Promise<APIResponse<T>> {
|
|
123
|
+
return this.request<T>(endpoint, {
|
|
124
|
+
method: 'PUT',
|
|
125
|
+
body: data ? JSON.stringify(data) : undefined,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async trackEvent(event: TractStackEvent): Promise<APIResponse> {
|
|
130
|
+
return this.post('/api/v1/events', {
|
|
131
|
+
...event,
|
|
132
|
+
timestamp: event.timestamp || Date.now(),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async getContent(slug: string): Promise<APIResponse> {
|
|
137
|
+
return this.get(`/api/v1/content/${slug}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async getFragment(fragmentId: string): Promise<APIResponse> {
|
|
141
|
+
return this.get(`/api/v1/fragments/${fragmentId}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getTenantId(): string {
|
|
145
|
+
return this.tenantId;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
setTenantId(tenantId: string): void {
|
|
149
|
+
this.tenantId = tenantId;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async getContentMapWithTimestamp(
|
|
153
|
+
lastUpdated?: number
|
|
154
|
+
): Promise<APIResponse<{ data: any[]; lastUpdated: number }>> {
|
|
155
|
+
let endpoint = 'api/v1/content/full-map';
|
|
156
|
+
if (lastUpdated) {
|
|
157
|
+
endpoint += `?lastUpdated=${lastUpdated}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Use the raw request method to get the full response
|
|
161
|
+
const response = await this.request(endpoint);
|
|
162
|
+
|
|
163
|
+
// For this endpoint, the backend returns {data: [...], lastUpdated: 123} directly
|
|
164
|
+
// So response.data IS the {data: [...], lastUpdated: 123} object
|
|
165
|
+
return response as APIResponse<{ data: any[]; lastUpdated: number }>;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function handleAPIResponse<T>(
|
|
170
|
+
response: APIResponse<T>,
|
|
171
|
+
onSuccess?: (data: T) => void,
|
|
172
|
+
onError?: (error: string) => void
|
|
173
|
+
): boolean {
|
|
174
|
+
if (response.success && response.data) {
|
|
175
|
+
onSuccess?.(response.data);
|
|
176
|
+
return true;
|
|
177
|
+
} else {
|
|
178
|
+
const error = response.error || 'Unknown error occurred';
|
|
179
|
+
onError?.(error);
|
|
180
|
+
console.error('TractStack API Error:', error);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin/Editor Authentication Utilities
|
|
3
|
+
* Uses role-specific cookies for secure admin authentication
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface AdminAuthClaims {
|
|
7
|
+
role: 'admin' | 'editor';
|
|
8
|
+
tenantId: string;
|
|
9
|
+
type: 'admin_auth';
|
|
10
|
+
iat: number;
|
|
11
|
+
exp: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if user is authenticated (either admin or editor)
|
|
16
|
+
*/
|
|
17
|
+
export function isAuthenticated(astro: any): boolean {
|
|
18
|
+
const adminCookie = astro.cookies.get('admin_auth');
|
|
19
|
+
const editorCookie = astro.cookies.get('editor_auth');
|
|
20
|
+
|
|
21
|
+
if (adminCookie?.value) {
|
|
22
|
+
const claims = validateAdminToken(adminCookie.value);
|
|
23
|
+
if (claims && claims.role === 'admin') return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (editorCookie?.value) {
|
|
27
|
+
const claims = validateAdminToken(editorCookie.value);
|
|
28
|
+
if (claims && claims.role === 'editor') return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if user has admin role
|
|
36
|
+
*/
|
|
37
|
+
export function isAdmin(astro: any): boolean {
|
|
38
|
+
const adminCookie = astro.cookies.get('admin_auth');
|
|
39
|
+
if (!adminCookie?.value) return false;
|
|
40
|
+
|
|
41
|
+
const claims = validateAdminToken(adminCookie.value);
|
|
42
|
+
return claims?.role === 'admin';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if user has editor role
|
|
47
|
+
*/
|
|
48
|
+
export function isEditor(astro: any): boolean {
|
|
49
|
+
const editorCookie = astro.cookies.get('editor_auth');
|
|
50
|
+
if (!editorCookie?.value) return false;
|
|
51
|
+
|
|
52
|
+
const claims = validateAdminToken(editorCookie.value);
|
|
53
|
+
return claims?.role === 'editor';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get user role (admin, editor, or null)
|
|
58
|
+
*/
|
|
59
|
+
export function getUserRole(astro: any): 'admin' | 'editor' | null {
|
|
60
|
+
const adminCookie = astro.cookies.get('admin_auth');
|
|
61
|
+
if (adminCookie?.value) {
|
|
62
|
+
const claims = validateAdminToken(adminCookie.value);
|
|
63
|
+
if (claims?.role === 'admin') return 'admin';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const editorCookie = astro.cookies.get('editor_auth');
|
|
67
|
+
if (editorCookie?.value) {
|
|
68
|
+
const claims = validateAdminToken(editorCookie.value);
|
|
69
|
+
if (claims?.role === 'editor') return 'editor';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Page-level protection: Require admin role
|
|
77
|
+
* Returns redirect response if unauthorized, undefined if authorized
|
|
78
|
+
*/
|
|
79
|
+
export function requireAdmin(astro: any): Response | undefined {
|
|
80
|
+
if (!isAdmin(astro)) {
|
|
81
|
+
return astro.redirect('/storykeep/login');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Page-level protection: Require editor role
|
|
87
|
+
* Returns redirect response if unauthorized, undefined if authorized
|
|
88
|
+
*/
|
|
89
|
+
export function requireEditor(astro: any): Response | undefined {
|
|
90
|
+
if (!isEditor(astro)) {
|
|
91
|
+
return astro.redirect('/storykeep/login');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Page-level protection: Require admin OR editor role
|
|
97
|
+
* Returns redirect response if unauthorized, undefined if authorized
|
|
98
|
+
*/
|
|
99
|
+
export function requireAdminOrEditor(astro: any): Response | undefined {
|
|
100
|
+
if (!isAuthenticated(astro)) {
|
|
101
|
+
return astro.redirect('/storykeep/login');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get admin token for API requests
|
|
107
|
+
* Returns the JWT token from the appropriate cookie
|
|
108
|
+
*/
|
|
109
|
+
export function getAdminToken(astro: any): string | null {
|
|
110
|
+
const adminCookie = astro.cookies.get('admin_auth');
|
|
111
|
+
if (adminCookie?.value) {
|
|
112
|
+
const claims = validateAdminToken(adminCookie.value);
|
|
113
|
+
if (claims?.role === 'admin') return adminCookie.value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const editorCookie = astro.cookies.get('editor_auth');
|
|
117
|
+
if (editorCookie?.value) {
|
|
118
|
+
const claims = validateAdminToken(editorCookie.value);
|
|
119
|
+
if (claims?.role === 'editor') return editorCookie.value;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Validate JWT token and extract claims
|
|
127
|
+
* Note: This is a simplified client-side validation
|
|
128
|
+
* Real validation happens on the backend
|
|
129
|
+
*/
|
|
130
|
+
function validateAdminToken(token: string): AdminAuthClaims | null {
|
|
131
|
+
try {
|
|
132
|
+
// Split JWT token
|
|
133
|
+
const parts = token.split('.');
|
|
134
|
+
if (parts.length !== 3) return null;
|
|
135
|
+
|
|
136
|
+
// Decode payload (base64url)
|
|
137
|
+
const payload = parts[1];
|
|
138
|
+
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
|
|
139
|
+
const claims = JSON.parse(decoded) as AdminAuthClaims;
|
|
140
|
+
|
|
141
|
+
// Basic validation
|
|
142
|
+
if (claims.type !== 'admin_auth') return null;
|
|
143
|
+
if (!claims.role || !['admin', 'editor'].includes(claims.role)) return null;
|
|
144
|
+
if (Date.now() / 1000 > claims.exp) return null; // Token expired
|
|
145
|
+
|
|
146
|
+
return claims;
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|