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,1020 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useStore } from '@nanostores/react';
|
|
3
|
+
import { createListCollection } from '@ark-ui/react/collection';
|
|
4
|
+
import { Select } from '@ark-ui/react/select';
|
|
5
|
+
import { RadioGroup } from '@ark-ui/react/radio-group';
|
|
6
|
+
import { Portal } from '@ark-ui/react/portal';
|
|
7
|
+
import CheckCircleIcon from '@heroicons/react/24/outline/CheckCircleIcon';
|
|
8
|
+
import ChevronLeftIcon from '@heroicons/react/24/outline/ChevronLeftIcon';
|
|
9
|
+
import ChevronRightIcon from '@heroicons/react/24/outline/ChevronRightIcon';
|
|
10
|
+
import { epinetCustomFilters } from '@/stores/analytics';
|
|
11
|
+
import EpinetTableView from './EpinetTableView';
|
|
12
|
+
import { MAX_ANALYTICS_HOURS } from '@/constants';
|
|
13
|
+
|
|
14
|
+
const getHourOptions = () => {
|
|
15
|
+
const hours = Array.from({ length: 24 }, (_, i) => ({
|
|
16
|
+
value: i.toString().padStart(2, '0'),
|
|
17
|
+
label: `${i.toString().padStart(2, '0')}:00`,
|
|
18
|
+
sortOrder: i,
|
|
19
|
+
}));
|
|
20
|
+
hours.push({ value: '24', label: '23:59', sortOrder: 24 });
|
|
21
|
+
hours.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
22
|
+
return hours.map(({ value, label }) => ({ value, label }));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getEndHourOptions = () => {
|
|
26
|
+
const hours = Array.from({ length: 24 }, (_, i) => ({
|
|
27
|
+
value: i.toString().padStart(2, '0'),
|
|
28
|
+
label: `${i.toString().padStart(2, '0')}:59`, // Always show :59 for end hours
|
|
29
|
+
sortOrder: i,
|
|
30
|
+
}));
|
|
31
|
+
hours.push({ value: '24', label: '23:59', sortOrder: 24 });
|
|
32
|
+
hours.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
33
|
+
return hours.map(({ value, label }) => ({ value, label }));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const hourOptions = getHourOptions();
|
|
37
|
+
const endHourOptions = getEndHourOptions();
|
|
38
|
+
|
|
39
|
+
interface ContentMapItem {
|
|
40
|
+
id: string;
|
|
41
|
+
title: string;
|
|
42
|
+
type: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface EpinetDurationSelectorProps {
|
|
46
|
+
fullContentMap?: ContentMapItem[];
|
|
47
|
+
isLoading?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const EpinetDurationSelector = ({
|
|
51
|
+
fullContentMap,
|
|
52
|
+
isLoading,
|
|
53
|
+
}: EpinetDurationSelectorProps = {}) => {
|
|
54
|
+
const [startDate, setStartDate] = useState<Date | null>(null);
|
|
55
|
+
const [endDate, setEndDate] = useState<Date | null>(null);
|
|
56
|
+
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
|
|
57
|
+
const [currentUserPage, setCurrentUserPage] = useState(0);
|
|
58
|
+
const [hasLocalChanges, setHasLocalChanges] = useState(false);
|
|
59
|
+
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
60
|
+
const [isApplying, setIsApplying] = useState(false);
|
|
61
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
62
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
63
|
+
const [showTable, setShowTable] = useState(false);
|
|
64
|
+
const usersPerPage = 50;
|
|
65
|
+
|
|
66
|
+
const $epinetCustomFilters = useStore(epinetCustomFilters);
|
|
67
|
+
|
|
68
|
+
const [localFilters, setLocalFilters] = useState({
|
|
69
|
+
visitorType: $epinetCustomFilters.visitorType || 'all',
|
|
70
|
+
selectedUserId: $epinetCustomFilters.selectedUserId || null,
|
|
71
|
+
startHour: '00',
|
|
72
|
+
endHour: '23:59',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const now = new Date();
|
|
77
|
+
const endDate = new Date(now);
|
|
78
|
+
let startDate = new Date(endDate);
|
|
79
|
+
startDate.setDate(startDate.getDate() - 7); // Default to one week
|
|
80
|
+
|
|
81
|
+
setStartDate(startDate);
|
|
82
|
+
setEndDate(endDate);
|
|
83
|
+
const currentHour = now.getHours().toString().padStart(2, '0');
|
|
84
|
+
setLocalFilters((prev) => ({
|
|
85
|
+
...prev,
|
|
86
|
+
startHour: currentHour,
|
|
87
|
+
endHour: currentHour,
|
|
88
|
+
}));
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
// MODIFIED: Enhanced sync from store
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if ($epinetCustomFilters.startTimeUTC && $epinetCustomFilters.endTimeUTC) {
|
|
94
|
+
const startUTC = new Date($epinetCustomFilters.startTimeUTC);
|
|
95
|
+
const endUTC = new Date($epinetCustomFilters.endTimeUTC);
|
|
96
|
+
|
|
97
|
+
// Update local dates and hours to match store
|
|
98
|
+
setStartDate(
|
|
99
|
+
new Date(
|
|
100
|
+
startUTC.getFullYear(),
|
|
101
|
+
startUTC.getMonth(),
|
|
102
|
+
startUTC.getDate()
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
setEndDate(
|
|
106
|
+
new Date(endUTC.getFullYear(), endUTC.getMonth(), endUTC.getDate())
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
setLocalFilters((prev) => ({
|
|
110
|
+
...prev,
|
|
111
|
+
startHour: startUTC.getHours().toString().padStart(2, '0'),
|
|
112
|
+
endHour: endUTC.getHours().toString().padStart(2, '0'),
|
|
113
|
+
visitorType: $epinetCustomFilters.visitorType || 'all',
|
|
114
|
+
selectedUserId: $epinetCustomFilters.selectedUserId,
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
setHasLocalChanges(false);
|
|
118
|
+
} else {
|
|
119
|
+
setLocalFilters((prev) => ({
|
|
120
|
+
...prev,
|
|
121
|
+
visitorType: $epinetCustomFilters.visitorType || 'all',
|
|
122
|
+
selectedUserId: $epinetCustomFilters.selectedUserId,
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
}, [$epinetCustomFilters]);
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
setCurrentUserPage(0);
|
|
129
|
+
}, [localFilters.visitorType]);
|
|
130
|
+
|
|
131
|
+
// Handle UnsavedChangesBar-style visibility animations
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (hasLocalChanges && !isVisible) {
|
|
134
|
+
setIsVisible(true);
|
|
135
|
+
setTimeout(() => setIsAnimating(true), 10); // Trigger entrance animation
|
|
136
|
+
} else if (!hasLocalChanges && isVisible && !isApplying) {
|
|
137
|
+
setIsAnimating(false);
|
|
138
|
+
setTimeout(() => setIsVisible(false), 300); // Brief delay before hiding
|
|
139
|
+
}
|
|
140
|
+
}, [hasLocalChanges, isVisible, isApplying]);
|
|
141
|
+
|
|
142
|
+
const visitorTypes = [
|
|
143
|
+
{ id: 'all', title: 'All Traffic', description: 'All visitors' },
|
|
144
|
+
{ id: 'anonymous', title: 'Anonymous', description: 'Anonymous visitors' },
|
|
145
|
+
{ id: 'known', title: 'Known Leads', description: 'Known visitors' },
|
|
146
|
+
] as const;
|
|
147
|
+
|
|
148
|
+
const updateVisitorType = (type: 'all' | 'anonymous' | 'known') => {
|
|
149
|
+
setLocalFilters((prev) => ({
|
|
150
|
+
...prev,
|
|
151
|
+
visitorType: type,
|
|
152
|
+
selectedUserId: null,
|
|
153
|
+
}));
|
|
154
|
+
setHasLocalChanges(true);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const updateSelectedUser = (userId: string | null) => {
|
|
158
|
+
setLocalFilters((prev) => ({ ...prev, selectedUserId: userId }));
|
|
159
|
+
setHasLocalChanges(true);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const updateHour = (type: 'startHour' | 'endHour', hour: string) => {
|
|
163
|
+
setLocalFilters((prev) => ({ ...prev, [type]: hour }));
|
|
164
|
+
setHasLocalChanges(true);
|
|
165
|
+
setErrorMessage(null);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// Create UTC datetime from local date and hour selection
|
|
169
|
+
const createUTCDateTime = (date: Date, hourStr: string): Date => {
|
|
170
|
+
const localDateTime = new Date(date);
|
|
171
|
+
const hour = hourStr === '24' ? 23 : parseInt(hourStr);
|
|
172
|
+
const minute = hourStr === '24' ? 59 : 0;
|
|
173
|
+
localDateTime.setHours(hour, minute, 0, 0);
|
|
174
|
+
|
|
175
|
+
// Convert to UTC by creating new Date with UTC values
|
|
176
|
+
return new Date(localDateTime.getTime());
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const updateDateRange = async () => {
|
|
180
|
+
if (!startDate || !endDate) {
|
|
181
|
+
setErrorMessage('Please select both start and end dates.');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const startUTCTime = createUTCDateTime(startDate, localFilters.startHour);
|
|
186
|
+
const endUTCTime = createUTCDateTime(endDate, localFilters.endHour);
|
|
187
|
+
|
|
188
|
+
if (endUTCTime <= startUTCTime) {
|
|
189
|
+
setErrorMessage('End time must be after start time.');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const nowUTC = new Date();
|
|
194
|
+
const maxPastTime = new Date(
|
|
195
|
+
nowUTC.getTime() - MAX_ANALYTICS_HOURS * 60 * 60 * 1000
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
if (startUTCTime < maxPastTime) {
|
|
199
|
+
setErrorMessage(
|
|
200
|
+
`Start time cannot be more than ${MAX_ANALYTICS_HOURS} hours in the past.`
|
|
201
|
+
);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (endUTCTime > nowUTC) {
|
|
206
|
+
setErrorMessage('End time cannot be in the future.');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
setIsApplying(true);
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
// Update with UTC timestamps
|
|
214
|
+
epinetCustomFilters.set(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
|
|
215
|
+
...$epinetCustomFilters,
|
|
216
|
+
visitorType: localFilters.visitorType,
|
|
217
|
+
selectedUserId: localFilters.selectedUserId,
|
|
218
|
+
startTimeUTC: startUTCTime.toISOString(),
|
|
219
|
+
endTimeUTC: endUTCTime.toISOString(),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
setHasLocalChanges(false);
|
|
223
|
+
setErrorMessage(null);
|
|
224
|
+
|
|
225
|
+
// Brief delay to show success state
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
setIsApplying(false);
|
|
228
|
+
}, 1000);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
setIsApplying(false);
|
|
231
|
+
setErrorMessage('Failed to apply filters. Please try again.');
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const handleDateChange = (
|
|
236
|
+
type: 'start' | 'end',
|
|
237
|
+
dateValue: string | null
|
|
238
|
+
) => {
|
|
239
|
+
if (!dateValue) {
|
|
240
|
+
setErrorMessage('Please select a valid date.');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const newDate = new Date(
|
|
245
|
+
parseInt(dateValue.split('-')[0]),
|
|
246
|
+
parseInt(dateValue.split('-')[1]) - 1,
|
|
247
|
+
parseInt(dateValue.split('-')[2])
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const nowUTC = new Date();
|
|
251
|
+
const maxPastTime = new Date(
|
|
252
|
+
nowUTC.getTime() - MAX_ANALYTICS_HOURS * 60 * 60 * 1000
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (newDate < maxPastTime) {
|
|
256
|
+
setErrorMessage(
|
|
257
|
+
`Date cannot be more than ${MAX_ANALYTICS_HOURS} hours in the past.`
|
|
258
|
+
);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (type === 'start') {
|
|
263
|
+
setStartDate(newDate);
|
|
264
|
+
} else {
|
|
265
|
+
setEndDate(newDate);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
setHasLocalChanges(true);
|
|
269
|
+
setErrorMessage(null);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const formatDateDisplay = (date: Date | null) => {
|
|
273
|
+
if (!date) return 'Select date';
|
|
274
|
+
return date.toLocaleDateString(undefined, {
|
|
275
|
+
year: 'numeric',
|
|
276
|
+
month: 'short',
|
|
277
|
+
day: 'numeric',
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const formatDateHourDisplay = (
|
|
282
|
+
date: Date,
|
|
283
|
+
hourStr: string,
|
|
284
|
+
isEndTime = false
|
|
285
|
+
) => {
|
|
286
|
+
if (!date) return '';
|
|
287
|
+
|
|
288
|
+
const localDateTime = new Date(date);
|
|
289
|
+
const hour = hourStr === '24' ? 23 : parseInt(hourStr);
|
|
290
|
+
// For end times, always show :59 to represent the full hour
|
|
291
|
+
const minute = isEndTime ? 59 : hourStr === '24' ? 59 : 0;
|
|
292
|
+
localDateTime.setHours(hour, minute, 0, 0);
|
|
293
|
+
|
|
294
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
localDateTime.toLocaleString(undefined, {
|
|
298
|
+
year: 'numeric',
|
|
299
|
+
month: '2-digit',
|
|
300
|
+
day: '2-digit',
|
|
301
|
+
hour: '2-digit',
|
|
302
|
+
minute: '2-digit',
|
|
303
|
+
hour12: true,
|
|
304
|
+
timeZone,
|
|
305
|
+
}) + ` (${timeZone})`
|
|
306
|
+
);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// Display current UTC range in local timezone with proper end time formatting
|
|
310
|
+
const formatCurrentUTCRange = () => {
|
|
311
|
+
const { startTimeUTC, endTimeUTC } = $epinetCustomFilters;
|
|
312
|
+
if (!startTimeUTC || !endTimeUTC) return '';
|
|
313
|
+
|
|
314
|
+
const startLocal = new Date(startTimeUTC);
|
|
315
|
+
const endLocal = new Date(endTimeUTC);
|
|
316
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
317
|
+
|
|
318
|
+
const formatTime = (date: Date, isEndTime = false) => {
|
|
319
|
+
// For end times, always show :59 for presentation
|
|
320
|
+
const displayDate = isEndTime ? new Date(date.getTime()) : date;
|
|
321
|
+
if (isEndTime) {
|
|
322
|
+
displayDate.setMinutes(59);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return displayDate.toLocaleString(undefined, {
|
|
326
|
+
year: 'numeric',
|
|
327
|
+
month: '2-digit',
|
|
328
|
+
day: '2-digit',
|
|
329
|
+
hour: '2-digit',
|
|
330
|
+
minute: '2-digit',
|
|
331
|
+
hour12: true,
|
|
332
|
+
timeZone,
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return `${formatTime(startLocal, false)} to ${formatTime(endLocal, true)} (${timeZone})`;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const paginatedUserCounts = ($epinetCustomFilters.userCounts || []).slice(
|
|
340
|
+
currentUserPage * usersPerPage,
|
|
341
|
+
(currentUserPage + 1) * usersPerPage
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const totalUserPages = Math.ceil(
|
|
345
|
+
(($epinetCustomFilters.userCounts || []).length || 0) / usersPerPage
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const nextUserPage = () => {
|
|
349
|
+
if (currentUserPage < totalUserPages - 1)
|
|
350
|
+
setCurrentUserPage(currentUserPage + 1);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const prevUserPage = () => {
|
|
354
|
+
if (currentUserPage > 0) setCurrentUserPage(currentUserPage - 1);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const maxDate = new Date();
|
|
358
|
+
const minDate = new Date();
|
|
359
|
+
minDate.setHours(minDate.getHours() - MAX_ANALYTICS_HOURS);
|
|
360
|
+
|
|
361
|
+
const setPresetDateRange = (period: string) => {
|
|
362
|
+
const nowUTC = new Date();
|
|
363
|
+
let hoursBack: number;
|
|
364
|
+
|
|
365
|
+
if (period === '24h') {
|
|
366
|
+
hoursBack = 24;
|
|
367
|
+
} else if (period === '7d') {
|
|
368
|
+
hoursBack = 168;
|
|
369
|
+
} else if (period === '28d') {
|
|
370
|
+
hoursBack = 672;
|
|
371
|
+
} else {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const startTimeUTC = new Date(
|
|
376
|
+
nowUTC.getTime() - hoursBack * 60 * 60 * 1000
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
// Convert to local dates and set local state
|
|
380
|
+
const startLocal = new Date(startTimeUTC);
|
|
381
|
+
const endLocal = new Date(nowUTC);
|
|
382
|
+
|
|
383
|
+
setStartDate(
|
|
384
|
+
new Date(
|
|
385
|
+
startLocal.getFullYear(),
|
|
386
|
+
startLocal.getMonth(),
|
|
387
|
+
startLocal.getDate()
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
setEndDate(
|
|
391
|
+
new Date(endLocal.getFullYear(), endLocal.getMonth(), endLocal.getDate())
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
setLocalFilters((prev) => ({
|
|
395
|
+
...prev,
|
|
396
|
+
startHour: startLocal.getHours().toString().padStart(2, '0'),
|
|
397
|
+
endHour: endLocal.getHours().toString().padStart(2, '0'),
|
|
398
|
+
}));
|
|
399
|
+
|
|
400
|
+
setHasLocalChanges(true);
|
|
401
|
+
setIsDatePickerOpen(false);
|
|
402
|
+
setErrorMessage(null);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const cancelChanges = () => {
|
|
406
|
+
const now = new Date();
|
|
407
|
+
setLocalFilters({
|
|
408
|
+
visitorType: $epinetCustomFilters.visitorType || 'all',
|
|
409
|
+
selectedUserId: $epinetCustomFilters.selectedUserId || null,
|
|
410
|
+
startHour: now.getHours().toString().padStart(2, '0'),
|
|
411
|
+
endHour: now.getHours().toString().padStart(2, '0'),
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const endDate = new Date(now);
|
|
415
|
+
let startDate = new Date(endDate);
|
|
416
|
+
startDate.setDate(startDate.getDate() - 7);
|
|
417
|
+
|
|
418
|
+
setStartDate(startDate);
|
|
419
|
+
setEndDate(endDate);
|
|
420
|
+
setIsDatePickerOpen(false);
|
|
421
|
+
setHasLocalChanges(false);
|
|
422
|
+
setErrorMessage(null);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const radioGroupStyles = `
|
|
426
|
+
.radio-control[data-state="unchecked"] .radio-dot { background-color: #d1d5db; }
|
|
427
|
+
.radio-control[data-state="checked"] .radio-dot { background-color: #0891b2; }
|
|
428
|
+
.radio-control[data-state="checked"] { border-color: #0891b2; }
|
|
429
|
+
.radio-item { border: 1px solid #d1d5db; }
|
|
430
|
+
.radio-item[data-state="checked"] { border-color: #0891b2; }
|
|
431
|
+
.radio-item:hover { background-color: #f3f4f6; }
|
|
432
|
+
@media (max-width: 640px) { .radio-item { flex: 1 1 100%; } }
|
|
433
|
+
@media (min-width: 641px) { .radio-item { flex: 1 1 calc(33.333% - 0.5rem); } }
|
|
434
|
+
`;
|
|
435
|
+
|
|
436
|
+
const getFilterStatusMessage = () => {
|
|
437
|
+
const needsApply = hasLocalChanges;
|
|
438
|
+
const prefix = needsApply ? 'Press Apply Filters to load' : 'Showing';
|
|
439
|
+
|
|
440
|
+
let baseMessage: string;
|
|
441
|
+
if (needsApply && startDate && endDate) {
|
|
442
|
+
baseMessage = `${prefix} from ${formatDateHourDisplay(startDate, localFilters.startHour, false)} to ${formatDateHourDisplay(endDate, localFilters.endHour, true)}`;
|
|
443
|
+
} else if (
|
|
444
|
+
!needsApply &&
|
|
445
|
+
$epinetCustomFilters.startTimeUTC &&
|
|
446
|
+
$epinetCustomFilters.endTimeUTC
|
|
447
|
+
) {
|
|
448
|
+
baseMessage = `${prefix} ${formatCurrentUTCRange()}`;
|
|
449
|
+
} else {
|
|
450
|
+
baseMessage = `${prefix} from last 7 days`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const userInfo = needsApply
|
|
454
|
+
? localFilters.selectedUserId
|
|
455
|
+
? ` for individual user ${localFilters.selectedUserId}`
|
|
456
|
+
: ` for ${
|
|
457
|
+
localFilters.visitorType === 'all'
|
|
458
|
+
? 'all visitors'
|
|
459
|
+
: localFilters.visitorType === 'anonymous'
|
|
460
|
+
? 'anonymous visitors'
|
|
461
|
+
: 'known leads'
|
|
462
|
+
}`
|
|
463
|
+
: $epinetCustomFilters.selectedUserId
|
|
464
|
+
? ` for individual user ${$epinetCustomFilters.selectedUserId}`
|
|
465
|
+
: ` for ${
|
|
466
|
+
$epinetCustomFilters.visitorType === 'all'
|
|
467
|
+
? 'all visitors'
|
|
468
|
+
: $epinetCustomFilters.visitorType === 'anonymous'
|
|
469
|
+
? 'anonymous visitors'
|
|
470
|
+
: 'known leads'
|
|
471
|
+
}`;
|
|
472
|
+
|
|
473
|
+
return baseMessage + userInfo;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// Get bar styling based on current state
|
|
477
|
+
const getBarStyling = () => {
|
|
478
|
+
if (isApplying) {
|
|
479
|
+
return {
|
|
480
|
+
bgColor: 'bg-blue-50',
|
|
481
|
+
borderColor: 'border-blue-200',
|
|
482
|
+
iconColor: 'text-blue-600',
|
|
483
|
+
textColor: 'text-blue-800',
|
|
484
|
+
icon: (
|
|
485
|
+
<svg className="h-5 w-5 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
486
|
+
<circle
|
|
487
|
+
className="opacity-25"
|
|
488
|
+
cx="12"
|
|
489
|
+
cy="12"
|
|
490
|
+
r="10"
|
|
491
|
+
stroke="currentColor"
|
|
492
|
+
strokeWidth="4"
|
|
493
|
+
/>
|
|
494
|
+
<path
|
|
495
|
+
className="opacity-75"
|
|
496
|
+
fill="currentColor"
|
|
497
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
498
|
+
/>
|
|
499
|
+
</svg>
|
|
500
|
+
),
|
|
501
|
+
};
|
|
502
|
+
} else if (errorMessage) {
|
|
503
|
+
return {
|
|
504
|
+
bgColor: 'bg-red-50',
|
|
505
|
+
borderColor: 'border-red-200',
|
|
506
|
+
iconColor: 'text-red-600',
|
|
507
|
+
textColor: 'text-red-800',
|
|
508
|
+
icon: (
|
|
509
|
+
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
510
|
+
<path
|
|
511
|
+
fillRule="evenodd"
|
|
512
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
513
|
+
clipRule="evenodd"
|
|
514
|
+
/>
|
|
515
|
+
</svg>
|
|
516
|
+
),
|
|
517
|
+
};
|
|
518
|
+
} else {
|
|
519
|
+
return {
|
|
520
|
+
bgColor: 'bg-amber-50',
|
|
521
|
+
borderColor: 'border-amber-200',
|
|
522
|
+
iconColor: 'text-amber-600',
|
|
523
|
+
textColor: 'text-amber-800',
|
|
524
|
+
icon: (
|
|
525
|
+
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
526
|
+
<path
|
|
527
|
+
fillRule="evenodd"
|
|
528
|
+
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
|
|
529
|
+
clipRule="evenodd"
|
|
530
|
+
/>
|
|
531
|
+
</svg>
|
|
532
|
+
),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const styling = getBarStyling();
|
|
538
|
+
|
|
539
|
+
// Get message based on current state
|
|
540
|
+
const getMessage = () => {
|
|
541
|
+
if (isApplying) {
|
|
542
|
+
return 'Applying filters...';
|
|
543
|
+
} else if (errorMessage) {
|
|
544
|
+
return errorMessage;
|
|
545
|
+
} else {
|
|
546
|
+
return getFilterStatusMessage();
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
return (
|
|
551
|
+
<>
|
|
552
|
+
<div className="space-y-4">
|
|
553
|
+
{$epinetCustomFilters.enabled && (
|
|
554
|
+
<div
|
|
555
|
+
className={`space-y-4 rounded-lg border-2 border-dashed border-gray-200 bg-gray-50 p-4`}
|
|
556
|
+
>
|
|
557
|
+
<div
|
|
558
|
+
className="flex flex-col space-y-4 md:flex-row md:gap-4 md:space-y-0"
|
|
559
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}
|
|
560
|
+
>
|
|
561
|
+
<style>{`
|
|
562
|
+
@media (min-width: 768px) {
|
|
563
|
+
.duration-selector-container {
|
|
564
|
+
display: grid;
|
|
565
|
+
grid-template-columns: 200px 1fr;
|
|
566
|
+
gap: 1rem;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
`}</style>
|
|
570
|
+
|
|
571
|
+
<div className="duration-selector-container flex flex-col space-y-4 md:space-y-0">
|
|
572
|
+
{/* Left column: Narrow visitor types stacked */}
|
|
573
|
+
<div className="space-y-2">
|
|
574
|
+
<style>{radioGroupStyles}</style>
|
|
575
|
+
<RadioGroup.Root
|
|
576
|
+
value={localFilters.visitorType}
|
|
577
|
+
onValueChange={({ value }) =>
|
|
578
|
+
updateVisitorType(value as 'all' | 'anonymous' | 'known')
|
|
579
|
+
}
|
|
580
|
+
aria-label="Select visitor type"
|
|
581
|
+
>
|
|
582
|
+
<RadioGroup.Label className="sr-only">
|
|
583
|
+
Visitor Type
|
|
584
|
+
</RadioGroup.Label>
|
|
585
|
+
<div className="flex flex-wrap gap-2 md:flex-col md:gap-2">
|
|
586
|
+
{visitorTypes.map((type) => (
|
|
587
|
+
<RadioGroup.Item
|
|
588
|
+
key={type.id}
|
|
589
|
+
value={type.id}
|
|
590
|
+
className="radio-item relative flex cursor-pointer rounded-lg bg-white px-3 py-2 focus:outline-none"
|
|
591
|
+
>
|
|
592
|
+
<div className="flex w-full items-center justify-between">
|
|
593
|
+
<div className="flex items-center">
|
|
594
|
+
<RadioGroup.ItemControl className="radio-control mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-gray-300">
|
|
595
|
+
<div className="radio-dot h-2 w-2 rounded-full" />
|
|
596
|
+
</RadioGroup.ItemControl>
|
|
597
|
+
<RadioGroup.ItemText>
|
|
598
|
+
<div className="text-sm">
|
|
599
|
+
<p className="font-bold text-gray-800">
|
|
600
|
+
{type.title}
|
|
601
|
+
</p>
|
|
602
|
+
<span className="inline text-gray-600">
|
|
603
|
+
{type.description}
|
|
604
|
+
</span>
|
|
605
|
+
</div>
|
|
606
|
+
</RadioGroup.ItemText>
|
|
607
|
+
</div>
|
|
608
|
+
<div className="hidden shrink-0 text-cyan-600 data-[state=checked]:block">
|
|
609
|
+
<CheckCircleIcon className="h-5 w-5" />
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
<RadioGroup.ItemHiddenInput />
|
|
613
|
+
</RadioGroup.Item>
|
|
614
|
+
))}
|
|
615
|
+
</div>
|
|
616
|
+
</RadioGroup.Root>
|
|
617
|
+
</div>
|
|
618
|
+
|
|
619
|
+
{/* Right column: Date/time controls and user select in 2 rows */}
|
|
620
|
+
<div className="space-y-4">
|
|
621
|
+
{/* Row 1: Date Range | Start Hour | End Hour */}
|
|
622
|
+
<div className="date-time-row grid grid-cols-1 gap-4">
|
|
623
|
+
<style>{`
|
|
624
|
+
.date-time-row {
|
|
625
|
+
display: grid;
|
|
626
|
+
grid-template-columns: 1fr auto auto;
|
|
627
|
+
gap: 1rem;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
@media (max-width: 767px) {
|
|
631
|
+
.date-time-grid {
|
|
632
|
+
grid-template-columns: 1fr !important;
|
|
633
|
+
}
|
|
634
|
+
.date-time-row {
|
|
635
|
+
grid-template-columns: 1fr auto;
|
|
636
|
+
grid-template-rows: auto auto;
|
|
637
|
+
}
|
|
638
|
+
.date-time-row > div:nth-child(3) {
|
|
639
|
+
grid-column: 2;
|
|
640
|
+
grid-row: 2;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
`}</style>
|
|
644
|
+
<div className="date-time-grid space-y-1">
|
|
645
|
+
<div className="block text-sm font-bold text-gray-700">
|
|
646
|
+
Date Range (Local Time)
|
|
647
|
+
</div>
|
|
648
|
+
<div className="relative">
|
|
649
|
+
<button
|
|
650
|
+
onClick={() => setIsDatePickerOpen(!isDatePickerOpen)}
|
|
651
|
+
className="w-fit min-w-48 rounded-md border border-gray-300 bg-white px-3 py-2 text-left text-sm shadow-sm"
|
|
652
|
+
aria-label="Toggle date range picker"
|
|
653
|
+
>
|
|
654
|
+
{startDate && endDate
|
|
655
|
+
? `${formatDateDisplay(startDate)} - ${formatDateDisplay(endDate)}`
|
|
656
|
+
: 'Select date range'}
|
|
657
|
+
</button>
|
|
658
|
+
|
|
659
|
+
{isDatePickerOpen && (
|
|
660
|
+
<div className="absolute z-10 mt-1 w-full rounded-md bg-white p-2 shadow-lg sm:w-auto">
|
|
661
|
+
<div className="mb-2 flex flex-wrap justify-between gap-2">
|
|
662
|
+
<button
|
|
663
|
+
className="rounded-md p-1 text-sm hover:bg-gray-100"
|
|
664
|
+
onClick={() => setPresetDateRange('24h')}
|
|
665
|
+
>
|
|
666
|
+
Last 24 hours
|
|
667
|
+
</button>
|
|
668
|
+
<button
|
|
669
|
+
className="rounded-md p-1 text-sm hover:bg-gray-100"
|
|
670
|
+
onClick={() => setPresetDateRange('7d')}
|
|
671
|
+
>
|
|
672
|
+
Last 7 days
|
|
673
|
+
</button>
|
|
674
|
+
<button
|
|
675
|
+
className="rounded-md p-1 text-sm hover:bg-gray-100"
|
|
676
|
+
onClick={() => setPresetDateRange('28d')}
|
|
677
|
+
>
|
|
678
|
+
Last 28 days
|
|
679
|
+
</button>
|
|
680
|
+
<button
|
|
681
|
+
className="rounded-md p-1 text-sm hover:bg-gray-100"
|
|
682
|
+
onClick={() => setIsDatePickerOpen(false)}
|
|
683
|
+
>
|
|
684
|
+
Close
|
|
685
|
+
</button>
|
|
686
|
+
</div>
|
|
687
|
+
|
|
688
|
+
<div className="mb-2">
|
|
689
|
+
<p className="text-sm font-bold">
|
|
690
|
+
Start date: {formatDateDisplay(startDate)}
|
|
691
|
+
</p>
|
|
692
|
+
<p className="text-sm font-bold">
|
|
693
|
+
End date: {formatDateDisplay(endDate)}
|
|
694
|
+
</p>
|
|
695
|
+
</div>
|
|
696
|
+
|
|
697
|
+
<div className="flex flex-col gap-4 sm:flex-row">
|
|
698
|
+
<div className="flex-1">
|
|
699
|
+
<label
|
|
700
|
+
htmlFor="start-date"
|
|
701
|
+
className="block text-sm font-bold"
|
|
702
|
+
>
|
|
703
|
+
Start
|
|
704
|
+
</label>
|
|
705
|
+
<input
|
|
706
|
+
id="start-date"
|
|
707
|
+
type="date"
|
|
708
|
+
className="w-full rounded border px-2 py-1"
|
|
709
|
+
onChange={(e) =>
|
|
710
|
+
handleDateChange('start', e.target.value)
|
|
711
|
+
}
|
|
712
|
+
value={
|
|
713
|
+
startDate
|
|
714
|
+
? startDate.toISOString().split('T')[0]
|
|
715
|
+
: ''
|
|
716
|
+
}
|
|
717
|
+
min={minDate.toISOString().split('T')[0]}
|
|
718
|
+
max={maxDate.toISOString().split('T')[0]}
|
|
719
|
+
/>
|
|
720
|
+
</div>
|
|
721
|
+
<div className="flex-1">
|
|
722
|
+
<label
|
|
723
|
+
htmlFor="end-date"
|
|
724
|
+
className="block text-sm font-bold"
|
|
725
|
+
>
|
|
726
|
+
End
|
|
727
|
+
</label>
|
|
728
|
+
<input
|
|
729
|
+
id="end-date"
|
|
730
|
+
type="date"
|
|
731
|
+
className="w-full rounded border px-2 py-1"
|
|
732
|
+
onChange={(e) =>
|
|
733
|
+
handleDateChange('end', e.target.value)
|
|
734
|
+
}
|
|
735
|
+
value={
|
|
736
|
+
endDate
|
|
737
|
+
? endDate.toISOString().split('T')[0]
|
|
738
|
+
: ''
|
|
739
|
+
}
|
|
740
|
+
min={
|
|
741
|
+
startDate
|
|
742
|
+
? startDate.toISOString().split('T')[0]
|
|
743
|
+
: minDate.toISOString().split('T')[0]
|
|
744
|
+
}
|
|
745
|
+
max={maxDate.toISOString().split('T')[0]}
|
|
746
|
+
/>
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
</div>
|
|
750
|
+
)}
|
|
751
|
+
</div>
|
|
752
|
+
</div>
|
|
753
|
+
|
|
754
|
+
<div className="space-y-1" style={{ minWidth: '120px' }}>
|
|
755
|
+
<div className="block text-sm font-bold text-gray-700">
|
|
756
|
+
Start Hour
|
|
757
|
+
</div>
|
|
758
|
+
<select
|
|
759
|
+
id="start-hour"
|
|
760
|
+
value={localFilters.startHour}
|
|
761
|
+
onChange={(e) =>
|
|
762
|
+
updateHour('startHour', e.target.value)
|
|
763
|
+
}
|
|
764
|
+
className="w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-8 text-left text-sm text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-cyan-600"
|
|
765
|
+
>
|
|
766
|
+
{hourOptions.map((option) => (
|
|
767
|
+
<option key={option.value} value={option.value}>
|
|
768
|
+
{option.label}
|
|
769
|
+
</option>
|
|
770
|
+
))}
|
|
771
|
+
</select>
|
|
772
|
+
</div>
|
|
773
|
+
|
|
774
|
+
<div className="space-y-1" style={{ minWidth: '120px' }}>
|
|
775
|
+
<div className="block text-sm font-bold text-gray-700">
|
|
776
|
+
End Hour
|
|
777
|
+
</div>
|
|
778
|
+
<select
|
|
779
|
+
id="end-hour"
|
|
780
|
+
value={localFilters.endHour}
|
|
781
|
+
onChange={(e) => updateHour('endHour', e.target.value)}
|
|
782
|
+
className="w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-8 text-left text-sm text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-cyan-600"
|
|
783
|
+
>
|
|
784
|
+
{endHourOptions.map((option) => (
|
|
785
|
+
<option key={option.value} value={option.value}>
|
|
786
|
+
{option.label}
|
|
787
|
+
</option>
|
|
788
|
+
))}
|
|
789
|
+
</select>
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
|
|
793
|
+
{/* Row 2: User select (spans full width) */}
|
|
794
|
+
{paginatedUserCounts.length > 0 && (
|
|
795
|
+
<div
|
|
796
|
+
className="rounded-lg border border-gray-200 bg-white p-3 shadow-sm"
|
|
797
|
+
style={{ maxWidth: '500px' }}
|
|
798
|
+
>
|
|
799
|
+
<div className="mb-2 flex items-center justify-between">
|
|
800
|
+
<h3 className="text-sm font-bold text-gray-700">
|
|
801
|
+
View Individual User Journey
|
|
802
|
+
</h3>
|
|
803
|
+
{totalUserPages > 1 && (
|
|
804
|
+
<div className="flex items-center space-x-2 text-sm">
|
|
805
|
+
<button
|
|
806
|
+
onClick={prevUserPage}
|
|
807
|
+
disabled={currentUserPage === 0}
|
|
808
|
+
className="rounded-full p-1 hover:bg-gray-100 disabled:opacity-50 disabled:hover:bg-transparent"
|
|
809
|
+
aria-label="Previous page"
|
|
810
|
+
>
|
|
811
|
+
<ChevronLeftIcon className="h-5 w-5" />
|
|
812
|
+
</button>
|
|
813
|
+
<span>
|
|
814
|
+
Page {currentUserPage + 1} of {totalUserPages}
|
|
815
|
+
</span>
|
|
816
|
+
<button
|
|
817
|
+
onClick={nextUserPage}
|
|
818
|
+
disabled={currentUserPage >= totalUserPages - 1}
|
|
819
|
+
className="rounded-full p-1 hover:bg-gray-100 disabled:opacity-50 disabled:hover:bg-transparent"
|
|
820
|
+
aria-label="Next page"
|
|
821
|
+
>
|
|
822
|
+
<ChevronRightIcon className="h-5 w-5" />
|
|
823
|
+
</button>
|
|
824
|
+
</div>
|
|
825
|
+
)}
|
|
826
|
+
</div>
|
|
827
|
+
|
|
828
|
+
<div>
|
|
829
|
+
<Select.Root
|
|
830
|
+
collection={createListCollection({
|
|
831
|
+
items: [
|
|
832
|
+
{ value: '', label: 'Select user' },
|
|
833
|
+
...paginatedUserCounts.map((user) => ({
|
|
834
|
+
value: user.id,
|
|
835
|
+
label: `${user.id} (${user.count} events)`,
|
|
836
|
+
})),
|
|
837
|
+
],
|
|
838
|
+
})}
|
|
839
|
+
value={
|
|
840
|
+
localFilters.selectedUserId
|
|
841
|
+
? [localFilters.selectedUserId]
|
|
842
|
+
: ['']
|
|
843
|
+
}
|
|
844
|
+
onValueChange={({ value }) =>
|
|
845
|
+
updateSelectedUser(value[0] || null)
|
|
846
|
+
}
|
|
847
|
+
>
|
|
848
|
+
<Select.Label className="sr-only">
|
|
849
|
+
Select user
|
|
850
|
+
</Select.Label>
|
|
851
|
+
<Select.Control>
|
|
852
|
+
<Select.Trigger
|
|
853
|
+
className="relative w-fit min-w-64 cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-sm text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-cyan-600"
|
|
854
|
+
aria-label="Select individual user"
|
|
855
|
+
>
|
|
856
|
+
<Select.ValueText placeholder="Select user">
|
|
857
|
+
{localFilters.selectedUserId
|
|
858
|
+
? localFilters.selectedUserId
|
|
859
|
+
: 'Select user'}
|
|
860
|
+
</Select.ValueText>
|
|
861
|
+
<Select.Indicator className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
|
862
|
+
<span className="h-5 w-5 text-gray-500">▼</span>
|
|
863
|
+
</Select.Indicator>
|
|
864
|
+
</Select.Trigger>
|
|
865
|
+
</Select.Control>
|
|
866
|
+
<Portal>
|
|
867
|
+
<Select.Positioner>
|
|
868
|
+
<Select.Content className="z-10 mt-2 max-h-96 w-[var(--trigger-width)] overflow-auto rounded-md bg-white text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
|
869
|
+
{paginatedUserCounts.length > 0 ? (
|
|
870
|
+
[
|
|
871
|
+
<Select.Item
|
|
872
|
+
key="empty"
|
|
873
|
+
item={{ value: '', label: 'Select user' }}
|
|
874
|
+
className="cursor-pointer select-none p-2 text-sm text-gray-500 hover:bg-slate-100 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
|
|
875
|
+
>
|
|
876
|
+
<Select.ItemText>
|
|
877
|
+
Select user
|
|
878
|
+
</Select.ItemText>
|
|
879
|
+
</Select.Item>,
|
|
880
|
+
...paginatedUserCounts.map((user) => (
|
|
881
|
+
<Select.Item
|
|
882
|
+
key={user.id}
|
|
883
|
+
item={{
|
|
884
|
+
value: user.id,
|
|
885
|
+
label: `${user.id} (${user.count} events)`,
|
|
886
|
+
}}
|
|
887
|
+
className="cursor-pointer select-none p-2 text-sm text-gray-700 hover:bg-slate-100 data-[highlighted]:bg-cyan-600 data-[highlighted]:text-white"
|
|
888
|
+
>
|
|
889
|
+
<Select.ItemText>
|
|
890
|
+
{user.id}{' '}
|
|
891
|
+
<span className="text-xs text-gray-500">
|
|
892
|
+
({user.count} events)
|
|
893
|
+
</span>
|
|
894
|
+
</Select.ItemText>
|
|
895
|
+
</Select.Item>
|
|
896
|
+
)),
|
|
897
|
+
]
|
|
898
|
+
) : (
|
|
899
|
+
<div className="p-2 text-sm text-gray-500">
|
|
900
|
+
No users available
|
|
901
|
+
</div>
|
|
902
|
+
)}
|
|
903
|
+
</Select.Content>
|
|
904
|
+
</Select.Positioner>
|
|
905
|
+
</Portal>
|
|
906
|
+
</Select.Root>
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
)}
|
|
910
|
+
</div>
|
|
911
|
+
</div>
|
|
912
|
+
</div>
|
|
913
|
+
|
|
914
|
+
{/* Current status display - only show when no changes pending */}
|
|
915
|
+
{!hasLocalChanges && $epinetCustomFilters.enabled && (
|
|
916
|
+
<div className="flex items-center justify-between rounded-md bg-gray-100 p-2">
|
|
917
|
+
<p className="text-sm font-bold text-gray-700">
|
|
918
|
+
{getFilterStatusMessage()}
|
|
919
|
+
</p>
|
|
920
|
+
<button
|
|
921
|
+
onClick={() => setShowTable(!showTable)}
|
|
922
|
+
className="rounded-md bg-cyan-600 px-3 py-1 text-sm text-white shadow-sm transition-colors duration-200 hover:bg-cyan-700"
|
|
923
|
+
>
|
|
924
|
+
{showTable ? 'Hide' : 'Show'} Table View
|
|
925
|
+
</button>
|
|
926
|
+
</div>
|
|
927
|
+
)}
|
|
928
|
+
|
|
929
|
+
{showTable && (
|
|
930
|
+
<EpinetTableView
|
|
931
|
+
fullContentMap={fullContentMap || []}
|
|
932
|
+
isLoading={isLoading}
|
|
933
|
+
/>
|
|
934
|
+
)}
|
|
935
|
+
</div>
|
|
936
|
+
)}
|
|
937
|
+
</div>
|
|
938
|
+
|
|
939
|
+
{/* UnsavedChangesBar-style sticky bottom bar */}
|
|
940
|
+
{isVisible && (
|
|
941
|
+
<div
|
|
942
|
+
className={`fixed bottom-0 left-0 right-0 z-50 transform pr-12 transition-all duration-300 ease-in-out ${
|
|
943
|
+
isAnimating
|
|
944
|
+
? 'translate-y-0 opacity-100'
|
|
945
|
+
: 'translate-y-full opacity-0'
|
|
946
|
+
}`}
|
|
947
|
+
>
|
|
948
|
+
{/* Backdrop blur overlay */}
|
|
949
|
+
<div className="absolute inset-0 bg-black/10 backdrop-blur-sm" />
|
|
950
|
+
|
|
951
|
+
{/* Main content bar */}
|
|
952
|
+
<div className="relative mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
|
|
953
|
+
<div
|
|
954
|
+
className={`flex items-center justify-between rounded-lg border px-6 py-4 shadow-lg ${styling.bgColor} ${styling.borderColor}`}
|
|
955
|
+
>
|
|
956
|
+
{/* Icon + message */}
|
|
957
|
+
<div className="flex items-center space-x-3">
|
|
958
|
+
<div className={`flex-shrink-0 ${styling.iconColor}`}>
|
|
959
|
+
{styling.icon}
|
|
960
|
+
</div>
|
|
961
|
+
<div>
|
|
962
|
+
<p className={`text-sm font-bold ${styling.textColor}`}>
|
|
963
|
+
{getMessage()}
|
|
964
|
+
</p>
|
|
965
|
+
{errorMessage && !isApplying && (
|
|
966
|
+
<p className="mt-1 text-xs text-red-600">
|
|
967
|
+
Click Apply Filters to try again
|
|
968
|
+
</p>
|
|
969
|
+
)}
|
|
970
|
+
</div>
|
|
971
|
+
</div>
|
|
972
|
+
|
|
973
|
+
{/* Action buttons */}
|
|
974
|
+
<div className="flex items-center space-x-3">
|
|
975
|
+
{/* Cancel button - only show when not applying */}
|
|
976
|
+
{!isApplying && (
|
|
977
|
+
<button
|
|
978
|
+
type="button"
|
|
979
|
+
onClick={cancelChanges}
|
|
980
|
+
className={`rounded-md border px-3 py-2 text-sm font-bold shadow-sm transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 ${
|
|
981
|
+
errorMessage
|
|
982
|
+
? 'border-red-300 bg-white text-red-800 hover:bg-red-50 focus:ring-red-500'
|
|
983
|
+
: 'border-amber-300 bg-white text-amber-800 hover:bg-amber-50 focus:ring-amber-500'
|
|
984
|
+
}`}
|
|
985
|
+
>
|
|
986
|
+
Cancel
|
|
987
|
+
</button>
|
|
988
|
+
)}
|
|
989
|
+
|
|
990
|
+
{/* Apply Filters button - only show when changes exist and not already applied */}
|
|
991
|
+
{hasLocalChanges && (
|
|
992
|
+
<button
|
|
993
|
+
type="button"
|
|
994
|
+
onClick={updateDateRange}
|
|
995
|
+
disabled={!startDate || !endDate || isApplying}
|
|
996
|
+
className={`rounded-md border border-transparent px-4 py-2 text-sm font-bold text-white shadow-sm transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed ${
|
|
997
|
+
isApplying
|
|
998
|
+
? 'bg-blue-500 opacity-75'
|
|
999
|
+
: errorMessage
|
|
1000
|
+
? startDate && endDate
|
|
1001
|
+
? 'bg-red-600 hover:bg-red-700 focus:ring-red-500'
|
|
1002
|
+
: 'bg-red-400 opacity-50'
|
|
1003
|
+
: startDate && endDate
|
|
1004
|
+
? 'bg-amber-600 hover:bg-amber-700 focus:ring-amber-500'
|
|
1005
|
+
: 'bg-amber-400 opacity-50'
|
|
1006
|
+
}`}
|
|
1007
|
+
>
|
|
1008
|
+
{isApplying ? 'Applying...' : 'Apply Filters'}
|
|
1009
|
+
</button>
|
|
1010
|
+
)}
|
|
1011
|
+
</div>
|
|
1012
|
+
</div>
|
|
1013
|
+
</div>
|
|
1014
|
+
</div>
|
|
1015
|
+
)}
|
|
1016
|
+
</>
|
|
1017
|
+
);
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
export default EpinetDurationSelector;
|