@flamingo-stack/openframe-frontend-core 0.0.296 → 0.0.297
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/README.md +9 -0
- package/dist/{chunk-7RIYT7ZH.js → chunk-2QG57XOJ.js} +1067 -205
- package/dist/chunk-2QG57XOJ.js.map +1 -0
- package/dist/{chunk-WHMATDVP.js → chunk-3JIQVE7T.js} +9 -15
- package/dist/{chunk-WHMATDVP.js.map → chunk-3JIQVE7T.js.map} +1 -1
- package/dist/{chunk-GLLDTKZK.cjs → chunk-4PSQS3SW.cjs} +7 -9
- package/dist/chunk-4PSQS3SW.cjs.map +1 -0
- package/dist/{chunk-OY7OF7E7.js → chunk-4TLE6VLU.js} +30 -24
- package/dist/chunk-4TLE6VLU.js.map +1 -0
- package/dist/{chunk-W6M2FLLT.cjs → chunk-53FUMSZ5.cjs} +40 -46
- package/dist/chunk-53FUMSZ5.cjs.map +1 -0
- package/dist/{chunk-D3LEFMOA.cjs → chunk-54KNMC2R.cjs} +3 -3
- package/dist/{chunk-D3LEFMOA.cjs.map → chunk-54KNMC2R.cjs.map} +1 -1
- package/dist/{chunk-EYEW6PTA.cjs → chunk-6C526VNN.cjs} +358 -118
- package/dist/chunk-6C526VNN.cjs.map +1 -0
- package/dist/{chunk-XREEV72C.cjs → chunk-7OVGB2DQ.cjs} +19 -25
- package/dist/chunk-7OVGB2DQ.cjs.map +1 -0
- package/dist/{chunk-6GCI7JOE.js → chunk-AD6C23QY.js} +8 -7
- package/dist/{chunk-6GCI7JOE.js.map → chunk-AD6C23QY.js.map} +1 -1
- package/dist/chunk-F5OB2YAL.cjs +144 -0
- package/dist/chunk-F5OB2YAL.cjs.map +1 -0
- package/dist/chunk-FBWXMMRB.cjs +2 -0
- package/dist/chunk-FBWXMMRB.cjs.map +1 -0
- package/dist/{chunk-YIGPRLQY.cjs → chunk-FCDQNTDG.cjs} +21 -20
- package/dist/chunk-FCDQNTDG.cjs.map +1 -0
- package/dist/{chunk-IE6OU3WQ.cjs → chunk-FQOTC3UU.cjs} +318 -16
- package/dist/chunk-FQOTC3UU.cjs.map +1 -0
- package/dist/{chunk-QHIXS3W2.cjs → chunk-GUTS7HGA.cjs} +11590 -2105
- package/dist/chunk-GUTS7HGA.cjs.map +1 -0
- package/dist/chunk-GZ4C3XW6.js +2 -0
- package/dist/chunk-GZ4C3XW6.js.map +1 -0
- package/dist/{chunk-5P3B2LZW.js → chunk-IL47XWV5.js} +8 -14
- package/dist/{chunk-5P3B2LZW.js.map → chunk-IL47XWV5.js.map} +1 -1
- package/dist/{chunk-LCNMR277.js → chunk-IZ7JSBFP.js} +1 -1
- package/dist/chunk-IZ7JSBFP.js.map +1 -0
- package/dist/{chunk-EL6QLAWX.js → chunk-JALO4TAZ.js} +357 -55
- package/dist/chunk-JALO4TAZ.js.map +1 -0
- package/dist/{chunk-AQOWFSMB.cjs → chunk-L6PSSIUQ.cjs} +1 -1
- package/dist/chunk-L6PSSIUQ.cjs.map +1 -0
- package/dist/{chunk-MBFWU2EM.js → chunk-L7ULJKG7.js} +6 -10
- package/dist/{chunk-MBFWU2EM.js.map → chunk-L7ULJKG7.js.map} +1 -1
- package/dist/{chunk-K2PFPBMF.js → chunk-PC746XCO.js} +15050 -5565
- package/dist/chunk-PC746XCO.js.map +1 -0
- package/dist/{chunk-3ZXUQQL4.js → chunk-PI4WSYQV.js} +2 -2
- package/dist/{chunk-E4XABBSU.js → chunk-PWQUAVA3.js} +338 -98
- package/dist/chunk-PWQUAVA3.js.map +1 -0
- package/dist/chunk-SA2WPJVO.js +144 -0
- package/dist/chunk-SA2WPJVO.js.map +1 -0
- package/dist/{chunk-X6BV7MB7.cjs → chunk-UNVE2SDJ.cjs} +37 -31
- package/dist/chunk-UNVE2SDJ.cjs.map +1 -0
- package/dist/{chunk-5E2HOSSH.cjs → chunk-WMSTJAZT.cjs} +913 -51
- package/dist/chunk-WMSTJAZT.cjs.map +1 -0
- package/dist/{chunk-ZP4AVIZP.js → chunk-X4DOXQRT.js} +4 -6
- package/dist/{chunk-ZP4AVIZP.js.map → chunk-X4DOXQRT.js.map} +1 -1
- package/dist/{chunk-X647HY3F.cjs → chunk-YBYI62OE.cjs} +33 -37
- package/dist/chunk-YBYI62OE.cjs.map +1 -0
- package/dist/components/case-studies/index.cjs +126 -0
- package/dist/components/case-studies/index.cjs.map +1 -0
- package/dist/components/case-studies/index.d.ts +2 -0
- package/dist/components/case-studies/index.d.ts.map +1 -0
- package/dist/components/case-studies/index.js +126 -0
- package/dist/components/case-studies/index.js.map +1 -0
- package/dist/components/case-studies/share-experience-section.d.ts +48 -0
- package/dist/components/case-studies/share-experience-section.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +8 -18
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +75 -85
- package/dist/components/contact/index.cjs +8 -15
- package/dist/components/contact/index.cjs.map +1 -1
- package/dist/components/contact/index.js +7 -14
- package/dist/components/docs/doc-viewer.d.ts +39 -2
- package/dist/components/docs/doc-viewer.d.ts.map +1 -1
- package/dist/components/docs/docs-hub-page.d.ts +46 -0
- package/dist/components/docs/docs-hub-page.d.ts.map +1 -0
- package/dist/components/docs/index.cjs +17 -9
- package/dist/components/docs/index.cjs.map +1 -1
- package/dist/components/docs/index.d.ts +4 -0
- package/dist/components/docs/index.d.ts.map +1 -1
- package/dist/components/docs/index.js +16 -8
- package/dist/components/docs/skeletons.d.ts +32 -0
- package/dist/components/docs/skeletons.d.ts.map +1 -0
- package/dist/components/docs/use-docs-resolve-link.d.ts +20 -0
- package/dist/components/docs/use-docs-resolve-link.d.ts.map +1 -0
- package/dist/components/docs/use-document-tree.d.ts.map +1 -1
- package/dist/components/embeds/embed-container.d.ts +37 -0
- package/dist/components/embeds/embed-container.d.ts.map +1 -0
- package/dist/components/embeds/embed-iframe.d.ts.map +1 -1
- package/dist/components/embeds/file-download-card.d.ts +18 -0
- package/dist/components/embeds/file-download-card.d.ts.map +1 -0
- package/dist/components/embeds/index.cjs +38 -15
- package/dist/components/embeds/index.cjs.map +1 -1
- package/dist/components/embeds/index.d.ts +8 -0
- package/dist/components/embeds/index.d.ts.map +1 -1
- package/dist/components/embeds/index.js +40 -17
- package/dist/components/embeds/linkedin-embed-client.d.ts +8 -0
- package/dist/components/embeds/linkedin-embed-client.d.ts.map +1 -0
- package/dist/components/embeds/markdown-image.d.ts +5 -0
- package/dist/components/embeds/markdown-image.d.ts.map +1 -0
- package/dist/components/embeds/reddit-embed-client.d.ts +7 -0
- package/dist/components/embeds/reddit-embed-client.d.ts.map +1 -0
- package/dist/components/embeds/rich-markdown-runtime.d.ts +46 -0
- package/dist/components/embeds/rich-markdown-runtime.d.ts.map +1 -0
- package/dist/components/embeds/twitter-embed-client.d.ts +8 -0
- package/dist/components/embeds/twitter-embed-client.d.ts.map +1 -0
- package/dist/components/faq/index.cjs +9 -16
- package/dist/components/faq/index.cjs.map +1 -1
- package/dist/components/faq/index.js +8 -15
- package/dist/components/features/index.cjs +8 -16
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +24 -32
- package/dist/components/index.cjs +257 -452
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +781 -976
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/page-header.d.ts +78 -0
- package/dist/components/layout/page-header.d.ts.map +1 -0
- package/dist/components/layout/page-layout.d.ts +10 -1
- package/dist/components/layout/page-layout.d.ts.map +1 -1
- package/dist/components/layout/page-with-header.d.ts +67 -0
- package/dist/components/layout/page-with-header.d.ts.map +1 -0
- package/dist/components/layout/title-block.d.ts +17 -1
- package/dist/components/layout/title-block.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +7 -15
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +9 -17
- package/dist/components/onboarding-guides/index.cjs +35 -36
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +13 -14
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +9 -16
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.js +8 -15
- package/dist/components/shared/dev-section/dev-section-page.d.ts +9 -0
- package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -1
- package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -1
- package/dist/components/shared/dev-section/index.d.ts +1 -1
- package/dist/components/shared/dev-section/index.d.ts.map +1 -1
- package/dist/components/shared/doc-search/use-doc-search.d.ts.map +1 -1
- package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +100 -112
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +20 -32
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/file-manager/index.cjs +50 -52
- package/dist/components/ui/file-manager/index.cjs.map +1 -1
- package/dist/components/ui/file-manager/index.js +4 -6
- package/dist/components/ui/file-manager/index.js.map +1 -1
- package/dist/components/ui/index.cjs +13 -19
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +133 -139
- package/dist/components/ui/release-changelog-section.d.ts +6 -2
- package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
- package/dist/components/ui/rich-markdown-renderer.d.ts +34 -0
- package/dist/components/ui/rich-markdown-renderer.d.ts.map +1 -0
- package/dist/components/ui/simple-markdown-renderer.d.ts +2 -8
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/contexts/chat-runtime-context.d.ts +14 -0
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +3 -3
- package/dist/contexts/index.js +5 -5
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +4 -4
- package/dist/hooks/index.cjs +4 -9
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +6 -11
- package/dist/index.cjs +14 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +362 -368
- package/dist/types/doc-source.d.ts +31 -1
- package/dist/types/doc-source.d.ts.map +1 -1
- package/dist/utils/index.cjs +4 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/page-header-constants.d.ts +15 -0
- package/dist/utils/page-header-constants.d.ts.map +1 -0
- package/dist/utils/social-embed-cache.d.ts +29 -0
- package/dist/utils/social-embed-cache.d.ts.map +1 -0
- package/package.json +7 -1
- package/src/components/case-studies/index.ts +4 -0
- package/src/components/case-studies/share-experience-section.tsx +185 -0
- package/src/components/chat/embeddable-chat.tsx +1 -1
- package/src/components/docs/doc-viewer.tsx +111 -19
- package/src/components/docs/docs-hub-page.tsx +149 -0
- package/src/components/docs/index.ts +17 -0
- package/src/components/docs/skeletons.tsx +138 -0
- package/src/components/docs/use-docs-resolve-link.ts +52 -0
- package/src/components/docs/use-document-tree.ts +21 -0
- package/src/components/embeds/embed-container.tsx +80 -0
- package/src/components/embeds/embed-iframe.tsx +7 -9
- package/src/components/embeds/file-download-card.tsx +54 -0
- package/src/components/embeds/index.ts +30 -0
- package/src/components/embeds/linkedin-embed-client.tsx +100 -0
- package/src/components/embeds/markdown-image.tsx +88 -0
- package/src/components/embeds/og-link-preview.tsx +13 -13
- package/src/components/embeds/reddit-embed-client.tsx +550 -0
- package/src/components/embeds/rich-markdown-runtime.tsx +79 -0
- package/src/components/embeds/twitter-embed-client.tsx +308 -0
- package/src/components/layout/page-header.tsx +182 -0
- package/src/components/layout/page-layout.tsx +14 -1
- package/src/components/layout/page-with-header.tsx +110 -0
- package/src/components/layout/title-block.tsx +40 -62
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +3 -3
- package/src/components/shared/dev-section/dev-section-page.tsx +9 -1
- package/src/components/shared/dev-section/dev-section-view.tsx +14 -9
- package/src/components/shared/dev-section/index.ts +1 -1
- package/src/components/shared/doc-search/use-doc-search.ts +7 -3
- package/src/components/shared/legal-document/legal-document-page.tsx +2 -2
- package/src/components/shared/product-release/release-detail-page.tsx +6 -4
- package/src/components/ui/index.ts +2 -0
- package/src/components/ui/release-changelog-section.tsx +7 -2
- package/src/components/ui/rich-markdown-renderer.tsx +1203 -0
- package/src/components/ui/simple-markdown-renderer.tsx +7 -11
- package/src/contexts/chat-runtime-context.tsx +14 -0
- package/src/types/doc-source.ts +33 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/page-header-constants.ts +15 -0
- package/src/utils/social-embed-cache.ts +391 -0
- package/dist/chunk-26PKDALD.js +0 -2379
- package/dist/chunk-26PKDALD.js.map +0 -1
- package/dist/chunk-3MCHAFHB.js +0 -89
- package/dist/chunk-3MCHAFHB.js.map +0 -1
- package/dist/chunk-5E2HOSSH.cjs.map +0 -1
- package/dist/chunk-66AANIOC.cjs +0 -619
- package/dist/chunk-66AANIOC.cjs.map +0 -1
- package/dist/chunk-6JINAOI7.cjs +0 -311
- package/dist/chunk-6JINAOI7.cjs.map +0 -1
- package/dist/chunk-7RIYT7ZH.js.map +0 -1
- package/dist/chunk-AQOWFSMB.cjs.map +0 -1
- package/dist/chunk-BOCFIKYS.cjs +0 -3009
- package/dist/chunk-BOCFIKYS.cjs.map +0 -1
- package/dist/chunk-D652TJBQ.js +0 -3009
- package/dist/chunk-D652TJBQ.js.map +0 -1
- package/dist/chunk-E4XABBSU.js.map +0 -1
- package/dist/chunk-EL6QLAWX.js.map +0 -1
- package/dist/chunk-EYEW6PTA.cjs.map +0 -1
- package/dist/chunk-FQJK446R.js +0 -1606
- package/dist/chunk-FQJK446R.js.map +0 -1
- package/dist/chunk-GLLDTKZK.cjs.map +0 -1
- package/dist/chunk-IE6OU3WQ.cjs.map +0 -1
- package/dist/chunk-J54Z3OCR.cjs +0 -1606
- package/dist/chunk-J54Z3OCR.cjs.map +0 -1
- package/dist/chunk-K2PFPBMF.js.map +0 -1
- package/dist/chunk-KXCRGTRN.cjs +0 -2379
- package/dist/chunk-KXCRGTRN.cjs.map +0 -1
- package/dist/chunk-LCNMR277.js.map +0 -1
- package/dist/chunk-LFGGF7OT.cjs +0 -449
- package/dist/chunk-LFGGF7OT.cjs.map +0 -1
- package/dist/chunk-M2OCXTNT.js +0 -311
- package/dist/chunk-M2OCXTNT.js.map +0 -1
- package/dist/chunk-ME4EVDFP.js +0 -619
- package/dist/chunk-ME4EVDFP.js.map +0 -1
- package/dist/chunk-OQ6X7ZOC.js +0 -449
- package/dist/chunk-OQ6X7ZOC.js.map +0 -1
- package/dist/chunk-OY7OF7E7.js.map +0 -1
- package/dist/chunk-POKKCWKF.js +0 -354
- package/dist/chunk-POKKCWKF.js.map +0 -1
- package/dist/chunk-QHIXS3W2.cjs.map +0 -1
- package/dist/chunk-TFSYSWPS.cjs +0 -89
- package/dist/chunk-TFSYSWPS.cjs.map +0 -1
- package/dist/chunk-W6M2FLLT.cjs.map +0 -1
- package/dist/chunk-X647HY3F.cjs.map +0 -1
- package/dist/chunk-X6BV7MB7.cjs.map +0 -1
- package/dist/chunk-XREEV72C.cjs.map +0 -1
- package/dist/chunk-YETA25JW.cjs +0 -354
- package/dist/chunk-YETA25JW.cjs.map +0 -1
- package/dist/chunk-YIGPRLQY.cjs.map +0 -1
- /package/dist/{chunk-3ZXUQQL4.js.map → chunk-PI4WSYQV.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/contexts/chat-runtime-context.tsx"],"sourcesContent":["'use client'\n\n/**\n * Chat runtime context — single seam for embedding the chat panel in a\n * different host (e.g. user1.openframe.ai reverse-proxying API calls\n * under /api/mingo-guide/* to hub.openframe.ai/api/*).\n *\n * Three concerns, one context:\n * 1. API endpoints: chatStreamUrl / approvalToolUrl / commandsUrl /\n * buildListUrl + attachment endpoints + chat-identity. The chat\n * reads them from runtime; hub vs embedded app supply different\n * strings via different providers.\n * 2. Navigation mode + callbacks: 'host' or 'embed' mode. Host wires\n * its own router/docNav via the optional `navigate` callback\n * (plain function, NOT a hook); embed forces new-tab via\n * `defaultContentOrigin` + lib's `resolveExternalNavigation`.\n * 3. Identity context: only `source` (required for localStorage\n * namespacing). The display identity (greeting first-name etc.)\n * comes from the server via `useChatIdentity()` — never injected\n * client-side, so it always matches the server-resolved auth.\n *\n * Sibling of EndpointsRuntimeContext (announcement bar, contact form,\n * access codes). Each runtime stays an independent React context so\n * embedders can opt into either feature without forcing the other.\n *\n * IMPORTANT for embedders: memoize the value passed to\n * `<ChatRuntimeContext.Provider value={...}>` (e.g. via React.useMemo).\n * Every change to its reference identity invalidates downstream\n * `useMemo` consumers (the chat input's slash-commands binding,\n * useNavLink's embed-resolution memo, useDocChat's streamFn factory).\n * The hub's `<HubRuntimeProvider>` already memoizes correctly with\n * stable deps. Embedded apps that build the value inline on each render\n * will pay an avoidable re-render cost across the entire chat tree.\n */\n\nimport { createContext, useContext, type ReactNode } from 'react'\n\nimport type { ComposeContentUrl } from '../utils/content-href'\n\n/**\n * Runtime config consumed by the chat panel.\n */\nexport interface ChatRuntime {\n endpoints: {\n /** POST streaming chat. Hub: '/api/docs/chat'. */\n chatStreamUrl: string\n /** POST agent approve/reject. Hub: '/api/chat/agent/confirm-tool'. */\n approvalToolUrl: string\n /** Customer-ticket agent endpoints (Help Center). OPTIONAL — when unset,\n * the ticket hooks fall back to the bare hub paths\n * (`/api/chat/agent/{find-ticket,ticket-action,list-engagements}`).\n * Embedders behind a reverse proxy set these to their proxied paths\n * (e.g. `/content/api/chat/agent/...`) so tickets route through the SAME\n * endpoint config + proxy as every other endpoint. */\n findTicketUrl?: string\n ticketActionUrl?: string\n listEngagementsUrl?: string\n /** GET slash-command catalog. Hub: '/api/docs/commands'. */\n commandsUrl: string\n /** GET per-platform empty-state config (admin-edited in\n * `/admin/chat-config`): `{ greeting, enabledRagTableIds, suggestedQueries }`.\n * Hub: '/api/docs/empty-state'. OPTIONAL — the in-app (host-mode) chat\n * injects these values as SSR props instead, so it leaves this unset.\n * Cross-origin EMBEDDERS (no server hop) set it to their proxied path\n * (e.g. '/content/api/docs/empty-state') so `<EmbeddableChat>` can fetch\n * the greeting / quick-action chips / RAG-source filter at runtime. When\n * unset, the chat falls back to the explicit `emptyStateGreeting` /\n * `suggestedQueries` / `enabledRagTableIds` props (or in-code defaults). */\n emptyStateUrl?: string\n /** Build entity-card list URL for a content type + ids. Hub delegates\n * to the rag-table-config registry; embedded app provides its own\n * per-type URL builder against the reverse proxy. Returns null when\n * the type has no list endpoint (caller skips rendering). */\n buildListUrl: (type: string, ids: string[]) => string | null\n /** Chat-attachment endpoints — added for the v2 attachment feature.\n *\n * Three concerns:\n * - `attachmentUploadUrl` — POSTed by the chat-attachment hook\n * to mint a Supabase signed-upload-URL + HMAC view token.\n * - `attachmentViewUrlPrefix` — embedded in markdown URLs the\n * chat hosts in user message bubbles (`![]()` / `[Attached]`).\n * Stored in chat history; chosen at SEND time. In host mode the\n * relative `/api/storage/view/chat-attachments/` is sufficient\n * (same-origin); embedders supply an absolute hub URL so the\n * browser can fetch cross-origin.\n * - `identityUrl` — GET endpoint the `useChatIdentity` hook\n * hits to learn the `{authTier, source, attachmentsEnabled}`\n * capability bag for the current session. Used beyond chat\n * (tickets / contact form / any embedded surface that needs\n * to identify the proxied customer), so the name has no\n * \"chat\" prefix even though the consuming hook still does. */\n attachmentUploadUrl: string\n attachmentViewUrlPrefix: string\n identityUrl: string\n /** Optional URL prefix for the image proxy (`<prefix>?url=<external>`).\n * When unset, lib's `getProxiedImageUrl` returns the original URL\n * unchanged. Hub default: '/api/image-proxy'. Embedders that don't\n * host an image-proxy route leave this undefined → images load\n * directly cross-origin (CORS-permitting). */\n imageProxyUrlPrefix?: string\n /** Optional list of hostnames that should bypass the image proxy\n * (rendered direct). Hub uses ['openmsp.ai']; embedders typically\n * leave it unset. Matches the `skipDomains` parameter of\n * `getProxiedImageUrl`. */\n imageProxySkipDomains?: string[]\n /** Supabase storage origin (e.g. `https://xyz.supabase.co`) — used\n * by `useVideoWarmup` to scope the `<link rel=\"preload\" as=\"video\">`\n * hint to MP4s the deployment actually hosts. Hub wires it via\n * `getSupabaseStorageOrigin()`; embedders without a Supabase\n * storage origin leave it unset (preload is then skipped; Mux/\n * YouTube preconnect still fires). */\n supabaseStorageOrigin?: string\n }\n navigation: {\n /** ONE knob, two behaviors:\n * - 'host' = use the host page's existing click-routing untouched.\n * The chat panel calls `navigate?.()` for in-app routing.\n * - 'embed' = guest inside another app: short-circuit at the top\n * of click handlers to force new-tab + absolutize via\n * resolveExternalNavigation. */\n mode: 'host' | 'embed'\n /** Embed-only fallback origin for relative URLs whose target platform\n * can't be inferred. Used by resolveExternalNavigation when\n * `targetPlatform` is null — without this, a relative `/foo` href would\n * window.open against the embedder's origin, which is WRONG.\n * Set to your content host (e.g. 'https://hub.openframe.ai').\n * Required by the embedded app whenever mode='embed'. */\n defaultContentOrigin?: string\n /** Override for opening external URLs. MUST BE SYNCHRONOUS —\n * Safari/Firefox block popups opened outside a direct user gesture.\n * Default: window.open(href, '_blank', 'noopener,noreferrer'). */\n openExternal?: (href: string) => void\n /** Optional in-app navigation callback (host-mode only).\n * Returns `true` if the host handled the click in-app\n * (router.push + docNav.navigate); returns `false`, `undefined`,\n * or `void` → lib falls back to window.location.assign(href).\n * Hub wires this via HubRuntimeProvider's HubNavigationWiring;\n * embedders not in Next.js leave it undefined. */\n navigate?: (input: { href: string; path?: string | null; targetPlatform?: string | null }) => boolean | void\n /** Optional new-tab decision callback. Returns true → lib opens in\n * new tab; false → same tab via `navigate`. Hub wires the existing\n * `decideNewTab` logic from use-nav-link.tsx (re-imports the pure\n * helper from lib). Embedders may omit; lib defaults to:\n * same-origin/same-platform → same tab, else new tab. */\n decideNewTab?: (args: { href: string; targetPlatform?: string | null }) => boolean\n }\n /** Optional OG placeholder URL builder. Returns a branded\n * `/api/og-placeholder?...` URL for the given title. Hub wires this\n * to its `buildOgPlaceholderUrl` (resolves CSS-var ODS colors to\n * hex via the static map). Embedders can wire any equivalent that\n * hits their own placeholder route — or omit, in which case entity\n * cards fall back to no placeholder.\n *\n * Pure synchronous function — NOT a hook. Callers wrap with\n * `useMemo`/`useOgPlaceholder` for memoization. */\n resolvePlaceholderUrl?: (\n title: string,\n options?: { site?: string; aspect?: 'wide' | 'square' },\n ) => string\n /** Optional content-URL composer. Returns the platform-aware href +\n * target-platform tuple for a content entity. Hub wires this to its\n * `buildContentURL(type, slug, extractPrimaryPlatform(platforms))`\n * pipeline so the lib catalog/detail views can derive cross-\n * platform hrefs without knowing the hub's platform topology\n * (openmsp.ai / openframe.app / flamingo.run / tmcg).\n *\n * THE single content-href authority for every embeddable surface — page\n * views (onboarding catalog/detail, releases) AND chat cards / chips /\n * search results all resolve content links through this one seam, so a\n * given type lands in the SAME place regardless of where it's rendered.\n * Embedders wire `makeComposeContentUrl({ hostedTypes, contentOrigin })`;\n * omit it and lib views fall back to a same-origin relative path\n * (`buildDefaultHref`).\n *\n * Takes a single `ComposeContentUrlInput`: `type` + `identifier` (page\n * views pass the slug; chat rows pass the id + `externalUrl`, whose path\n * yields the slug for in-app routing) + optional `platforms` /\n * `externalUrl` / `targetPlatform`. */\n composeContentUrl?: ComposeContentUrl\n /** Per-`documentType` doc-viewer targets — the UNIFIED, DYNAMIC replacement for\n * the single `chipBasePlatform` prop. Maps a doc-table documentType\n * (`'markdown'`, `'data_room_doc'`, …) → `{ platform, basePath }` for the PUBLIC\n * doc viewer that hosts it. Doc chips with no `externalUrl` resolve PER ROW to\n * `getBaseUrl(platform)/<basePath>/<path>`, so a chat mixing several doc sources\n * sends EACH to its own home (markdown→flamingo/knowledge-base,\n * data_room_doc→company-hub/data-room) instead of one static fallback. The hub\n * may keep using `chipBasePlatform` (one doc source per platform); embedders that\n * surface multiple doc sources wire this. Threaded into `resolveSourceRowCTA`. */\n docPlatformTargets?: Record<string, { platform: string; basePath: string }>\n /** Chat source / platform identifier — OPTIONAL. The hub sets it from\n * `currentPlatform()`; EMBEDDERS leave it unset and stay platform-agnostic.\n *\n * It is NOT required for chat to work. The wire resolves source server-side\n * (`/docs/chat|search|commands` reject any client `source`); the\n * same-tab-vs-new-tab link decision falls back to an origin comparison when\n * it's absent (`decideNewTab` → `isCrossOriginUrl`); and the localStorage\n * history namespace falls back to a stable constant. Set it only where the\n * client legitimately needs to know its platform a priori — i.e. the hub,\n * where several platforms share related origins so \"same platform\" can't be\n * inferred from a URL alone. */\n source?: string\n // NOTE: No `user` field. The chat's display identity (greeting\n // first-name, etc.) comes from the SERVER-resolved auth via\n // `useChatIdentity()` — the same identity the server uses to\n // authorize requests. Letting embedders pass a client-side `user`\n // would let it desync from the actual auth tier, causing greetings\n // like \"Hey Bob\" while the server treats the session as\n // alice@example.com. Single source of truth: the server.\n}\n\nexport const ChatRuntimeContext = createContext<ChatRuntime | null>(null)\n\n/**\n * Returns the active runtime, or null when no provider is mounted.\n * NULL is a first-class value — it signals \"no chat runtime configured.\"\n * Optional consumers fall back to no-op behavior; strict consumers\n * use `useRequiredChatRuntime` (below).\n */\nexport function useChatRuntime(): ChatRuntime | null {\n return useContext(ChatRuntimeContext)\n}\n\n/**\n * Strict variant used INSIDE the chat panel. Throws if no provider.\n * The hub guarantees one exists by mounting `<HubRuntimeProvider>` at\n * root; the embedded app mounts its own `<ChatRuntimeContext.Provider>`\n * at the tree root. In Jest / Storybook tests that render chat\n * internals directly, wrap with `<HubRuntimeProvider>` (hub defaults)\n * or supply `<ChatRuntimeContext.Provider value={mockedRuntime}>`.\n */\nexport function useRequiredChatRuntime(): ChatRuntime {\n const v = useContext(ChatRuntimeContext)\n if (!v) {\n throw new Error(\n '[chat-runtime] hook called outside a <ChatRuntimeContext.Provider>. ' +\n 'The hub mounts <HubRuntimeProvider> at root — this only fires when ' +\n 'chat internals are rendered above the provider tree. ' +\n 'Fix: ensure the rendering subtree descends from the runtime provider. ' +\n 'In tests/Storybook: wrap with <HubRuntimeProvider> or supply ' +\n 'a <ChatRuntimeContext.Provider value={mockedRuntime}>.',\n )\n }\n return v\n}\n"],"mappings":";;;AAmCA,SAAS,eAAe,kBAAkC;AA+KnD,IAAM,qBAAqB,cAAkC,IAAI;AAQjE,SAAS,iBAAqC;AACnD,SAAO,WAAW,kBAAkB;AACtC;AAUO,SAAS,yBAAsC;AACpD,QAAM,IAAI,WAAW,kBAAkB;AACvC,MAAI,CAAC,GAAG;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IAMF;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
package/dist/chunk-LFGGF7OT.cjs
DELETED
|
@@ -1,449 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
var _chunkLGLPNWS6cjs = require('./chunk-LGLPNWS6.cjs');
|
|
8
|
-
|
|
9
|
-
// src/utils/platform-config.tsx
|
|
10
|
-
var _lucidereact = require('lucide-react');
|
|
11
|
-
var _jsxruntime = require('react/jsx-runtime');
|
|
12
|
-
var platformIcons = {
|
|
13
|
-
openframe: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.OpenFrameLogo, { className: "h-5 w-5", lowerPathColor: "#FFC008", upperPathColor: "#ffffff" }),
|
|
14
|
-
openmsp: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.OpenmspLogo, { className: "h-5 w-5" }),
|
|
15
|
-
flamingo: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: "h-5 w-5", fill: "#EC4899" }),
|
|
16
|
-
"flamingo-teaser": /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: "h-5 w-5", fill: "#EC4899" }),
|
|
17
|
-
"marketing-hub": /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: "h-5 w-5", fill: "#F357BB" }),
|
|
18
|
-
"product-hub": /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: "h-5 w-5", fill: "#5EA62E" }),
|
|
19
|
-
"revenue-hub": /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: "h-5 w-5", fill: "#FFC008" }),
|
|
20
|
-
"people-hub": /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: "h-5 w-5", fill: "#5EFAF0" }),
|
|
21
|
-
"company-hub": /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: "h-5 w-5", fill: "#f36666" }),
|
|
22
|
-
tmcg: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.MiamiCyberGangLogoFaceOnly, { className: "h-5 w-5" }),
|
|
23
|
-
universal: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Globe, { className: "h-5 w-5 text-[#10B981]" })
|
|
24
|
-
};
|
|
25
|
-
var platformColors = {
|
|
26
|
-
openmsp: "bg-[#3B82F6]",
|
|
27
|
-
openframe: "bg-[#8B5CF6]",
|
|
28
|
-
flamingo: "bg-[#EC4899]",
|
|
29
|
-
"flamingo-teaser": "bg-[#F59E0B]",
|
|
30
|
-
"marketing-hub": "bg-[#F357BB]",
|
|
31
|
-
"product-hub": "bg-[#5EA62E]",
|
|
32
|
-
"revenue-hub": "bg-[#FFC008]",
|
|
33
|
-
"people-hub": "bg-[#5EFAF0]",
|
|
34
|
-
"company-hub": "bg-[#f36666]",
|
|
35
|
-
tmcg: "bg-[#FF6B6B]",
|
|
36
|
-
universal: "bg-[#10B981]"
|
|
37
|
-
};
|
|
38
|
-
var platformDisplayNames = {
|
|
39
|
-
openmsp: "OpenMSP",
|
|
40
|
-
openframe: "OpenFrame",
|
|
41
|
-
flamingo: "Flamingo",
|
|
42
|
-
"flamingo-teaser": "Flamingo Teaser",
|
|
43
|
-
"marketing-hub": "Flamingo Marketing Hub",
|
|
44
|
-
"product-hub": "Flamingo Product Hub",
|
|
45
|
-
"revenue-hub": "Flamingo Revenue Hub",
|
|
46
|
-
"people-hub": "Flamingo People Hub",
|
|
47
|
-
"company-hub": "Flamingo Company Hub",
|
|
48
|
-
tmcg: "TMCG",
|
|
49
|
-
universal: "Universal"
|
|
50
|
-
};
|
|
51
|
-
var platformDescriptions = {
|
|
52
|
-
openmsp: "Comprehensive directory and comparison platform for managed service providers (MSPs) and technology vendors. Reduce vendor costs and discover open-source alternatives.",
|
|
53
|
-
openframe: "AI-driven open-source security operations center (SOC) and endpoint detection platform for MSPs.",
|
|
54
|
-
flamingo: "AI-driven open-source OS for MSPs. Swap bloated vendor tools for open ones. Automate the boring crap. Take your margin back.",
|
|
55
|
-
"flamingo-teaser": "Preview of Flamingo - the AI-driven open-source OS for MSPs.",
|
|
56
|
-
tmcg: "The Miami Cyber Gang - A cybersecurity community focused on education and collaboration.",
|
|
57
|
-
universal: "Cross-platform universal content."
|
|
58
|
-
};
|
|
59
|
-
var platformSlogans = {
|
|
60
|
-
openmsp: "Find Your Perfect MSP Partner",
|
|
61
|
-
openframe: "Open-Source Security Operations",
|
|
62
|
-
flamingo: "Open-Source OS for MSPs",
|
|
63
|
-
"flamingo-teaser": "Coming Soon: Open-Source OS for MSPs",
|
|
64
|
-
tmcg: "Miami Cyber Community",
|
|
65
|
-
universal: "Universal Platform"
|
|
66
|
-
};
|
|
67
|
-
var platformHexColors = {
|
|
68
|
-
openmsp: "#FFC008",
|
|
69
|
-
openframe: "#FFC008",
|
|
70
|
-
flamingo: "#FF6B9D",
|
|
71
|
-
universal: "#FFC008",
|
|
72
|
-
"flamingo-teaser": "#F59E0B",
|
|
73
|
-
"marketing-hub": "#F357BB",
|
|
74
|
-
"product-hub": "#5EA62E",
|
|
75
|
-
"revenue-hub": "#FFC008",
|
|
76
|
-
"people-hub": "#5EFAF0",
|
|
77
|
-
"company-hub": "#f36666",
|
|
78
|
-
tmcg: "#FF6B6B"
|
|
79
|
-
};
|
|
80
|
-
var platformIconNames = {
|
|
81
|
-
openmsp: "openmsp-logo",
|
|
82
|
-
openframe: "openframe-logo",
|
|
83
|
-
flamingo: "flamingo-logo",
|
|
84
|
-
universal: "globe",
|
|
85
|
-
"flamingo-teaser": "flamingo-logo",
|
|
86
|
-
"marketing-hub": "flamingo-logo",
|
|
87
|
-
"product-hub": "flamingo-logo",
|
|
88
|
-
"revenue-hub": "flamingo-logo",
|
|
89
|
-
"people-hub": "flamingo-logo",
|
|
90
|
-
"company-hub": "flamingo-logo",
|
|
91
|
-
tmcg: "tmcg-logo"
|
|
92
|
-
};
|
|
93
|
-
function getDefaultColorForPlatform(platformName) {
|
|
94
|
-
return platformHexColors[platformName] || platformHexColors.universal;
|
|
95
|
-
}
|
|
96
|
-
function getDefaultIconForPlatform(platformName) {
|
|
97
|
-
return platformIconNames[platformName] || platformIconNames.universal;
|
|
98
|
-
}
|
|
99
|
-
function transformPlatformConfigsToOptions(platformConfigs) {
|
|
100
|
-
return platformConfigs.map((platform) => ({
|
|
101
|
-
id: platform.id,
|
|
102
|
-
// Database UUID for matching
|
|
103
|
-
name: platform.name,
|
|
104
|
-
// Platform name enum
|
|
105
|
-
displayName: platform.display_name,
|
|
106
|
-
// Human-readable name
|
|
107
|
-
description: platform.description,
|
|
108
|
-
icon: platformIcons[platform.name] || platformIcons.universal,
|
|
109
|
-
color: platformColors[platform.name] || platformColors.universal
|
|
110
|
-
}));
|
|
111
|
-
}
|
|
112
|
-
function getPlatformIcon(platformName) {
|
|
113
|
-
return platformIcons[platformName] || platformIcons.universal;
|
|
114
|
-
}
|
|
115
|
-
function getPlatformColor(platformName) {
|
|
116
|
-
return platformColors[platformName] || platformColors.universal;
|
|
117
|
-
}
|
|
118
|
-
function getPlatformDisplayName(platformName) {
|
|
119
|
-
return platformDisplayNames[platformName] || platformName;
|
|
120
|
-
}
|
|
121
|
-
function getPlatformDescription(platformName) {
|
|
122
|
-
return platformDescriptions[platformName] || platformName;
|
|
123
|
-
}
|
|
124
|
-
function getPlatformSlogan(platformName) {
|
|
125
|
-
return platformSlogans[platformName] || platformName;
|
|
126
|
-
}
|
|
127
|
-
function getSmallPlatformIcon(platformName) {
|
|
128
|
-
const className = "h-4 w-4 flex-shrink-0";
|
|
129
|
-
switch (platformName) {
|
|
130
|
-
case "openframe":
|
|
131
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.OpenFrameLogo, { className, lowerPathColor: "#FFC008", upperPathColor: "#ffffff" });
|
|
132
|
-
case "openmsp":
|
|
133
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.OpenmspLogo, { className, frontBubbleColor: "#f1f1f1", innerFrontBubbleColor: "#000000", backBubbleColor: "#FFC008" });
|
|
134
|
-
case "flamingo":
|
|
135
|
-
case "flamingo-teaser":
|
|
136
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: `${className}`, fill: "#EC4899" });
|
|
137
|
-
case "marketing-hub":
|
|
138
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-flamingo-pink-base)" });
|
|
139
|
-
case "product-hub":
|
|
140
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-attention-green-success)" });
|
|
141
|
-
case "revenue-hub":
|
|
142
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-attention-yellow-warning)" });
|
|
143
|
-
case "people-hub":
|
|
144
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-flamingo-cyan-base)" });
|
|
145
|
-
case "company-hub":
|
|
146
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-attention-red-error)" });
|
|
147
|
-
case "tmcg":
|
|
148
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.MiamiCyberGangLogoFaceOnly, { className });
|
|
149
|
-
case "universal":
|
|
150
|
-
default:
|
|
151
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Globe, { className });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function getPlatformIconComponent(platformName, className = "h-6 w-6") {
|
|
155
|
-
switch (platformName) {
|
|
156
|
-
case "openframe":
|
|
157
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.OpenFrameLogo, { className });
|
|
158
|
-
case "openmsp":
|
|
159
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.OpenmspLogo, { className, color: "#f1f1f1" });
|
|
160
|
-
case "flamingo":
|
|
161
|
-
case "flamingo-teaser":
|
|
162
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className: `${className} text-white` });
|
|
163
|
-
case "marketing-hub":
|
|
164
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-flamingo-pink-base)" });
|
|
165
|
-
case "product-hub":
|
|
166
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-attention-green-success)" });
|
|
167
|
-
case "revenue-hub":
|
|
168
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-attention-yellow-warning)" });
|
|
169
|
-
case "people-hub":
|
|
170
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-flamingo-cyan-base)" });
|
|
171
|
-
case "company-hub":
|
|
172
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.FlamingoLogo, { className, fill: "var(--ods-attention-red-error)" });
|
|
173
|
-
case "tmcg":
|
|
174
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLGLPNWS6cjs.MiamiCyberGangLogoFaceOnly, { size: 24, className });
|
|
175
|
-
case "universal":
|
|
176
|
-
default:
|
|
177
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Globe, { className });
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// src/types/tool.types.ts
|
|
182
|
-
var ToolTypeValues = {
|
|
183
|
-
TACTICAL_RMM: "TACTICAL_RMM",
|
|
184
|
-
FLEET_MDM: "FLEET_MDM",
|
|
185
|
-
MESHCENTRAL: "MESHCENTRAL",
|
|
186
|
-
AUTHENTIK: "AUTHENTIK",
|
|
187
|
-
OPENFRAME: "OPENFRAME",
|
|
188
|
-
OPENFRAME_CHAT: "OPENFRAME_CHAT",
|
|
189
|
-
OPENFRAME_CLIENT: "OPENFRAME_CLIENT",
|
|
190
|
-
OSQUERY: "OSQUERY",
|
|
191
|
-
SYSTEM: "SYSTEM"
|
|
192
|
-
};
|
|
193
|
-
var toolLabels = {
|
|
194
|
-
TACTICAL_RMM: "Tactical",
|
|
195
|
-
FLEET_MDM: "Fleet",
|
|
196
|
-
MESHCENTRAL: "MeshCentral",
|
|
197
|
-
AUTHENTIK: "Authentik",
|
|
198
|
-
OPENFRAME: "OpenFrame",
|
|
199
|
-
OPENFRAME_CHAT: "OpenFrame Chat",
|
|
200
|
-
OPENFRAME_CLIENT: "OpenFrame Client",
|
|
201
|
-
OSQUERY: "Osquery",
|
|
202
|
-
SYSTEM: "System"
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
// src/utils/access-code-client.ts
|
|
206
|
-
async function validateAccessCode(email, code, endpoints) {
|
|
207
|
-
try {
|
|
208
|
-
const response = await fetch(endpoints.validateUrl, {
|
|
209
|
-
method: "POST",
|
|
210
|
-
headers: {
|
|
211
|
-
"Content-Type": "application/json"
|
|
212
|
-
},
|
|
213
|
-
body: JSON.stringify({ email, code })
|
|
214
|
-
});
|
|
215
|
-
if (!response.ok) {
|
|
216
|
-
const error = await response.json().catch(() => ({}));
|
|
217
|
-
throw new Error(error.error || "Validation request failed");
|
|
218
|
-
}
|
|
219
|
-
return await response.json();
|
|
220
|
-
} catch (error) {
|
|
221
|
-
return {
|
|
222
|
-
valid: false,
|
|
223
|
-
message: error instanceof Error ? error.message : "Validation failed"
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
async function consumeAccessCode(email, code, endpoints) {
|
|
228
|
-
try {
|
|
229
|
-
const response = await fetch(endpoints.consumeUrl, {
|
|
230
|
-
method: "POST",
|
|
231
|
-
headers: {
|
|
232
|
-
"Content-Type": "application/json"
|
|
233
|
-
},
|
|
234
|
-
body: JSON.stringify({ email, code })
|
|
235
|
-
});
|
|
236
|
-
if (!response.ok) {
|
|
237
|
-
const error = await response.json().catch(() => ({}));
|
|
238
|
-
throw new Error(error.error || "Consumption request failed");
|
|
239
|
-
}
|
|
240
|
-
return await response.json();
|
|
241
|
-
} catch (error) {
|
|
242
|
-
return {
|
|
243
|
-
success: false,
|
|
244
|
-
consumed: false,
|
|
245
|
-
message: error instanceof Error ? error.message : "Consumption failed"
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
async function validateAndConsumeAccessCode(email, code, endpoints) {
|
|
250
|
-
const validation = await validateAccessCode(email, code, endpoints);
|
|
251
|
-
if (!validation.valid) {
|
|
252
|
-
return validation;
|
|
253
|
-
}
|
|
254
|
-
const consumption = await consumeAccessCode(email, code, endpoints);
|
|
255
|
-
return {
|
|
256
|
-
...validation,
|
|
257
|
-
consumed: consumption.consumed,
|
|
258
|
-
message: consumption.consumed ? `Access granted for ${validation.cohort_name}` : consumption.message || validation.message
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// src/utils/scroll-into-view.ts
|
|
263
|
-
var activeRaf = 0;
|
|
264
|
-
var teardownActive = null;
|
|
265
|
-
function cancelActiveScroll() {
|
|
266
|
-
if (activeRaf) {
|
|
267
|
-
cancelAnimationFrame(activeRaf);
|
|
268
|
-
activeRaf = 0;
|
|
269
|
-
}
|
|
270
|
-
if (teardownActive) {
|
|
271
|
-
teardownActive();
|
|
272
|
-
teardownActive = null;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
|
276
|
-
function getScrollableAncestor(el) {
|
|
277
|
-
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
278
|
-
const overflowY = getComputedStyle(node).overflowY;
|
|
279
|
-
if ((overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight) {
|
|
280
|
-
return node;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return null;
|
|
284
|
-
}
|
|
285
|
-
function scrollElementIntoView(target, options = {}) {
|
|
286
|
-
if (typeof window === "undefined" || !target) return;
|
|
287
|
-
const { headerOffset = 0, behavior = "smooth", adjustTargetY, durationMs = 320 } = options;
|
|
288
|
-
const container = getScrollableAncestor(target);
|
|
289
|
-
const readCurrent = () => container ? container.scrollTop : window.scrollY;
|
|
290
|
-
const writeTo = (y) => {
|
|
291
|
-
if (container) container.scrollTop = y;
|
|
292
|
-
else window.scrollTo(0, y);
|
|
293
|
-
};
|
|
294
|
-
const computeTarget = () => {
|
|
295
|
-
const raw = container ? container.scrollTop + (target.getBoundingClientRect().top - container.getBoundingClientRect().top) - headerOffset : target.getBoundingClientRect().top + window.scrollY - headerOffset;
|
|
296
|
-
const adjusted = adjustTargetY ? adjustTargetY(raw) : raw;
|
|
297
|
-
const maxScroll = container ? Math.max(0, container.scrollHeight - container.clientHeight) : Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
|
|
298
|
-
return Math.min(Math.max(0, adjusted), maxScroll);
|
|
299
|
-
};
|
|
300
|
-
cancelActiveScroll();
|
|
301
|
-
const prefersReduced = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
302
|
-
if (behavior === "instant" || behavior === "auto" || prefersReduced) {
|
|
303
|
-
writeTo(computeTarget());
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
let startY = null;
|
|
307
|
-
let startTime = 0;
|
|
308
|
-
const onUserGesture = () => cancelActiveScroll();
|
|
309
|
-
window.addEventListener("wheel", onUserGesture, { passive: true });
|
|
310
|
-
window.addEventListener("touchmove", onUserGesture, { passive: true });
|
|
311
|
-
teardownActive = () => {
|
|
312
|
-
window.removeEventListener("wheel", onUserGesture);
|
|
313
|
-
window.removeEventListener("touchmove", onUserGesture);
|
|
314
|
-
};
|
|
315
|
-
const step = (now) => {
|
|
316
|
-
if (startY === null) {
|
|
317
|
-
startY = readCurrent();
|
|
318
|
-
startTime = now;
|
|
319
|
-
}
|
|
320
|
-
const targetY = computeTarget();
|
|
321
|
-
const t = Math.min(1, (now - startTime) / durationMs);
|
|
322
|
-
const y = startY + (targetY - startY) * easeOutCubic(t);
|
|
323
|
-
writeTo(y);
|
|
324
|
-
if (t < 1) {
|
|
325
|
-
activeRaf = requestAnimationFrame(step);
|
|
326
|
-
} else {
|
|
327
|
-
writeTo(computeTarget());
|
|
328
|
-
activeRaf = 0;
|
|
329
|
-
if (teardownActive) {
|
|
330
|
-
teardownActive();
|
|
331
|
-
teardownActive = null;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
activeRaf = requestAnimationFrame(step);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// src/utils/same-page-hash-nav.ts
|
|
339
|
-
var STICKY_HEADER_OFFSET_PX = 96;
|
|
340
|
-
var HUB_HEADER_OFFSET_PX = 80;
|
|
341
|
-
function normalizeHashFragment(hash) {
|
|
342
|
-
if (!hash) return "";
|
|
343
|
-
const second = hash.indexOf("#", 1);
|
|
344
|
-
return second < 0 ? hash : hash.slice(0, second);
|
|
345
|
-
}
|
|
346
|
-
function navigateSamePageHash(target, options = {}) {
|
|
347
|
-
if (typeof window === "undefined") return false;
|
|
348
|
-
const { headerOffset = 0, history: historyMode = "push" } = options;
|
|
349
|
-
const normalizedTarget = target.startsWith("#") ? window.location.pathname + window.location.search + target : target;
|
|
350
|
-
let url;
|
|
351
|
-
try {
|
|
352
|
-
url = new URL(normalizedTarget, window.location.href);
|
|
353
|
-
} catch (e) {
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
if (url.origin !== window.location.origin || url.pathname !== window.location.pathname || url.search !== window.location.search) {
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
const current = window.location.pathname + window.location.search + window.location.hash;
|
|
360
|
-
const normalizedHash = normalizeHashFragment(url.hash);
|
|
361
|
-
if (process.env.NODE_ENV === "development" && normalizedHash !== url.hash) {
|
|
362
|
-
console.warn(
|
|
363
|
-
`[navigateSamePageHash] malformed fragment "${url.hash}" \u2192 normalizing to "${normalizedHash}". Fix the upstream composer.`
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
const next = url.pathname + url.search + normalizedHash;
|
|
367
|
-
const id = normalizedHash && normalizedHash !== "#" ? normalizedHash.slice(1) : "";
|
|
368
|
-
if (!id && next !== current) return false;
|
|
369
|
-
if (next !== current) {
|
|
370
|
-
const oldURL = window.location.href;
|
|
371
|
-
if (historyMode === "replace") {
|
|
372
|
-
window.history.replaceState(null, "", next);
|
|
373
|
-
} else {
|
|
374
|
-
window.history.pushState(null, "", next);
|
|
375
|
-
}
|
|
376
|
-
window.dispatchEvent(new HashChangeEvent("hashchange", {
|
|
377
|
-
oldURL,
|
|
378
|
-
newURL: window.location.href
|
|
379
|
-
}));
|
|
380
|
-
}
|
|
381
|
-
const el = id ? document.getElementById(id) : null;
|
|
382
|
-
if (id && !el && process.env.NODE_ENV === "development") {
|
|
383
|
-
console.warn(
|
|
384
|
-
`[navigateSamePageHash] anchor "#${id}" not found \u2014 scrolling to top.`
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
scrollElementIntoView(_nullishCoalesce(el, () => ( document.documentElement)), {
|
|
388
|
-
behavior: "smooth",
|
|
389
|
-
headerOffset
|
|
390
|
-
});
|
|
391
|
-
return true;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// src/utils/humanity-signals.ts
|
|
395
|
-
var HONEYPOT_FIELD = "contact_url_confirm";
|
|
396
|
-
var ELAPSED_MS_FIELD = "form_elapsed_ms";
|
|
397
|
-
var DEFAULT_MIN_FILL_MS = 700;
|
|
398
|
-
function extractHumanitySignals(body) {
|
|
399
|
-
const b = _nullishCoalesce(body, () => ( {}));
|
|
400
|
-
const rawHp = b[HONEYPOT_FIELD];
|
|
401
|
-
const honeypot = rawHp == null ? "" : String(rawHp);
|
|
402
|
-
const rawMs = b[ELAPSED_MS_FIELD];
|
|
403
|
-
const elapsedMs = typeof rawMs === "number" && Number.isFinite(rawMs) ? rawMs : null;
|
|
404
|
-
return { honeypot, elapsedMs };
|
|
405
|
-
}
|
|
406
|
-
function evaluateHumanitySignals(body, opts) {
|
|
407
|
-
const { honeypot, elapsedMs } = extractHumanitySignals(body);
|
|
408
|
-
if (honeypot.trim() !== "") return { ok: false, reason: "honeypot" };
|
|
409
|
-
if (elapsedMs !== null && elapsedMs < opts.minFillMs) return { ok: false, reason: "too_fast" };
|
|
410
|
-
return { ok: true };
|
|
411
|
-
}
|
|
412
|
-
var splitCsvEnv = (s) => _nullishCoalesce(_optionalChain([s, 'optionalAccess', _ => _.split, 'call', _2 => _2(","), 'access', _3 => _3.map, 'call', _4 => _4((t) => t.trim()), 'access', _5 => _5.filter, 'call', _6 => _6(Boolean)]), () => ( []));
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
exports.platformIcons = platformIcons; exports.platformColors = platformColors; exports.platformDisplayNames = platformDisplayNames; exports.platformDescriptions = platformDescriptions; exports.platformSlogans = platformSlogans; exports.platformHexColors = platformHexColors; exports.platformIconNames = platformIconNames; exports.getDefaultColorForPlatform = getDefaultColorForPlatform; exports.getDefaultIconForPlatform = getDefaultIconForPlatform; exports.transformPlatformConfigsToOptions = transformPlatformConfigsToOptions; exports.getPlatformIcon = getPlatformIcon; exports.getPlatformColor = getPlatformColor; exports.getPlatformDisplayName = getPlatformDisplayName; exports.getPlatformDescription = getPlatformDescription; exports.getPlatformSlogan = getPlatformSlogan; exports.getSmallPlatformIcon = getSmallPlatformIcon; exports.getPlatformIconComponent = getPlatformIconComponent; exports.ToolTypeValues = ToolTypeValues; exports.toolLabels = toolLabels; exports.validateAccessCode = validateAccessCode; exports.consumeAccessCode = consumeAccessCode; exports.validateAndConsumeAccessCode = validateAndConsumeAccessCode; exports.scrollElementIntoView = scrollElementIntoView; exports.STICKY_HEADER_OFFSET_PX = STICKY_HEADER_OFFSET_PX; exports.HUB_HEADER_OFFSET_PX = HUB_HEADER_OFFSET_PX; exports.normalizeHashFragment = normalizeHashFragment; exports.navigateSamePageHash = navigateSamePageHash; exports.HONEYPOT_FIELD = HONEYPOT_FIELD; exports.ELAPSED_MS_FIELD = ELAPSED_MS_FIELD; exports.DEFAULT_MIN_FILL_MS = DEFAULT_MIN_FILL_MS; exports.extractHumanitySignals = extractHumanitySignals; exports.evaluateHumanitySignals = evaluateHumanitySignals; exports.splitCsvEnv = splitCsvEnv;
|
|
449
|
-
//# sourceMappingURL=chunk-LFGGF7OT.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-LFGGF7OT.cjs","../src/utils/platform-config.tsx","../src/types/tool.types.ts","../src/utils/access-code-client.ts","../src/utils/scroll-into-view.ts","../src/utils/same-page-hash-nav.ts","../src/utils/humanity-signals.ts"],"names":[],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-LFGGF7OT.cjs","sourcesContent":[null,"import React from 'react';\nimport { OpenmspLogo, FlamingoLogo, OpenFrameLogo, MiamiCyberGangLogoFaceOnly } from '../components/icons';\nimport { Globe } from 'lucide-react';\nimport type { SelectableOption } from '../components/features';\nimport type { PlatformConfig } from '../types/platform';\n\n// Platform icons mapping with consistent colors matching app theme\nexport const platformIcons = {\n openframe: <OpenFrameLogo className=\"h-5 w-5\" lowerPathColor=\"#FFC008\" upperPathColor=\"#ffffff\" />,\n openmsp: <OpenmspLogo className=\"h-5 w-5\" />,\n flamingo: <FlamingoLogo className=\"h-5 w-5\" fill=\"#EC4899\" />,\n 'flamingo-teaser': <FlamingoLogo className=\"h-5 w-5\" fill=\"#EC4899\" />,\n 'marketing-hub': <FlamingoLogo className=\"h-5 w-5\" fill=\"#F357BB\" />,\n 'product-hub': <FlamingoLogo className=\"h-5 w-5\" fill=\"#5EA62E\" />,\n 'revenue-hub': <FlamingoLogo className=\"h-5 w-5\" fill=\"#FFC008\" />,\n 'people-hub': <FlamingoLogo className=\"h-5 w-5\" fill=\"#5EFAF0\" />,\n 'company-hub': <FlamingoLogo className=\"h-5 w-5\" fill=\"#f36666\" />,\n tmcg: <MiamiCyberGangLogoFaceOnly className=\"h-5 w-5\" />,\n universal: <Globe className=\"h-5 w-5 text-[#10B981]\" />\n};\n\n// Platform colors mapping\nexport const platformColors = {\n openmsp: 'bg-[#3B82F6]',\n openframe: 'bg-[#8B5CF6]',\n flamingo: 'bg-[#EC4899]',\n 'flamingo-teaser': 'bg-[#F59E0B]',\n 'marketing-hub': 'bg-[#F357BB]',\n 'product-hub': 'bg-[#5EA62E]',\n 'revenue-hub': 'bg-[#FFC008]',\n 'people-hub': 'bg-[#5EFAF0]',\n 'company-hub': 'bg-[#f36666]',\n tmcg: 'bg-[#FF6B6B]',\n universal: 'bg-[#10B981]'\n};\n\n// Platform display names for consistent naming across the app\nexport const platformDisplayNames = {\n openmsp: 'OpenMSP',\n openframe: 'OpenFrame',\n flamingo: 'Flamingo',\n 'flamingo-teaser': 'Flamingo Teaser',\n 'marketing-hub': 'Flamingo Marketing Hub',\n 'product-hub': 'Flamingo Product Hub',\n 'revenue-hub': 'Flamingo Revenue Hub',\n 'people-hub': 'Flamingo People Hub',\n 'company-hub': 'Flamingo Company Hub',\n tmcg: 'TMCG',\n universal: 'Universal'\n};\n\n// Platform descriptions for consistent messaging across the app\nexport const platformDescriptions = {\n openmsp: 'Comprehensive directory and comparison platform for managed service providers (MSPs) and technology vendors. Reduce vendor costs and discover open-source alternatives.',\n openframe: 'AI-driven open-source security operations center (SOC) and endpoint detection platform for MSPs.',\n flamingo: 'AI-driven open-source OS for MSPs. Swap bloated vendor tools for open ones. Automate the boring crap. Take your margin back.',\n 'flamingo-teaser': 'Preview of Flamingo - the AI-driven open-source OS for MSPs.',\n tmcg: 'The Miami Cyber Gang - A cybersecurity community focused on education and collaboration.',\n universal: 'Cross-platform universal content.'\n};\n\n// Platform slogans for branding consistency\nexport const platformSlogans = {\n openmsp: 'Find Your Perfect MSP Partner',\n openframe: 'Open-Source Security Operations',\n flamingo: 'Open-Source OS for MSPs',\n 'flamingo-teaser': 'Coming Soon: Open-Source OS for MSPs',\n tmcg: 'Miami Cyber Community',\n universal: 'Universal Platform'\n};\n\n// Platform hex colors for default configuration\nexport const platformHexColors = {\n openmsp: '#FFC008',\n openframe: '#FFC008',\n flamingo: '#FF6B9D',\n universal: '#FFC008',\n 'flamingo-teaser': '#F59E0B',\n 'marketing-hub': '#F357BB',\n 'product-hub': '#5EA62E',\n 'revenue-hub': '#FFC008',\n 'people-hub': '#5EFAF0',\n 'company-hub': '#f36666',\n tmcg: '#FF6B6B'\n};\n\n// Platform icon names for default configuration\nexport const platformIconNames = {\n openmsp: 'openmsp-logo',\n openframe: 'openframe-logo',\n flamingo: 'flamingo-logo',\n universal: 'globe',\n 'flamingo-teaser': 'flamingo-logo',\n 'marketing-hub': 'flamingo-logo',\n 'product-hub': 'flamingo-logo',\n 'revenue-hub': 'flamingo-logo',\n 'people-hub': 'flamingo-logo',\n 'company-hub': 'flamingo-logo',\n tmcg: 'tmcg-logo'\n};\n\n/**\n * Get default color for platform\n */\nexport function getDefaultColorForPlatform(platformName: string): string {\n return platformHexColors[platformName as keyof typeof platformHexColors] || platformHexColors.universal;\n}\n\n/**\n * Get default icon name for platform\n */\nexport function getDefaultIconForPlatform(platformName: string): string {\n return platformIconNames[platformName as keyof typeof platformIconNames] || platformIconNames.universal;\n}\n\nexport function transformPlatformConfigsToOptions(platformConfigs: PlatformConfig[]): SelectableOption[] {\n return platformConfigs.map((platform: PlatformConfig) => ({\n id: platform.id, // Database UUID for matching\n name: platform.name, // Platform name enum\n displayName: platform.display_name, // Human-readable name\n description: platform.description,\n icon: platformIcons[platform.name as keyof typeof platformIcons] || platformIcons.universal,\n color: platformColors[platform.name as keyof typeof platformColors] || platformColors.universal\n }));\n}\n\n/**\n * Get platform icon by name\n */\nexport function getPlatformIcon(platformName: string) {\n return platformIcons[platformName as keyof typeof platformIcons] || platformIcons.universal;\n}\n\n/**\n * Get platform color by name\n */\nexport function getPlatformColor(platformName: string) {\n return platformColors[platformName as keyof typeof platformColors] || platformColors.universal;\n}\n\n/**\n * Get platform display name by name\n */\nexport function getPlatformDisplayName(platformName: string): string {\n return platformDisplayNames[platformName as keyof typeof platformDisplayNames] || platformName;\n}\n\n/**\n * Get platform description by name\n */\nexport function getPlatformDescription(platformName: string): string {\n return platformDescriptions[platformName as keyof typeof platformDescriptions] || platformName;\n}\n\n/**\n * Get platform slogan by name\n */\nexport function getPlatformSlogan(platformName: string): string {\n return platformSlogans[platformName as keyof typeof platformSlogans] || platformName;\n}\n\n/**\n * Get small platform icon for filter buttons with white colors (4x4 size)\n */\nexport function getSmallPlatformIcon(platformName: string): React.ReactNode {\n const className = \"h-4 w-4 flex-shrink-0\";\n\n switch (platformName) {\n case 'openframe':\n return <OpenFrameLogo className={className} lowerPathColor=\"#FFC008\" upperPathColor=\"#ffffff\" />;\n case 'openmsp':\n return <OpenmspLogo className={className} frontBubbleColor=\"#f1f1f1\" innerFrontBubbleColor=\"#000000\" backBubbleColor=\"#FFC008\" />;\n case 'flamingo':\n case 'flamingo-teaser':\n return <FlamingoLogo className={`${className}`} fill=\"#EC4899\" />;\n case 'marketing-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-flamingo-pink-base)\" />;\n case 'product-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-attention-green-success)\" />;\n case 'revenue-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-attention-yellow-warning)\" />;\n case 'people-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-flamingo-cyan-base)\" />;\n case 'company-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-attention-red-error)\" />;\n case 'tmcg':\n return <MiamiCyberGangLogoFaceOnly className={className} />;\n case 'universal':\n default:\n return <Globe className={className} />;\n }\n}\n\n/**\n * Get platform icon for admin/selector components (standard 6x6 size)\n */\nexport function getPlatformIconComponent(platformName: string, className: string = \"h-6 w-6\"): React.ReactNode {\n switch (platformName) {\n case 'openframe':\n return <OpenFrameLogo className={className} />;\n case 'openmsp':\n return <OpenmspLogo className={className} color=\"#f1f1f1\" />;\n case 'flamingo':\n case 'flamingo-teaser':\n return <FlamingoLogo className={`${className} text-white`} />;\n case 'marketing-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-flamingo-pink-base)\" />;\n case 'product-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-attention-green-success)\" />;\n case 'revenue-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-attention-yellow-warning)\" />;\n case 'people-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-flamingo-cyan-base)\" />;\n case 'company-hub':\n return <FlamingoLogo className={className} fill=\"var(--ods-attention-red-error)\" />;\n case 'tmcg':\n return <MiamiCyberGangLogoFaceOnly size={24} className={className} />;\n case 'universal':\n default:\n return <Globe className={className} />;\n }\n}","/**\n * Centralized Tool Types\n *\n * Single source of truth for all tool-related types across the entire platform.\n * Used by ToolBadge, ToolIcon, and any component that needs tool type information.\n */\n\nexport const ToolTypeValues = {\n TACTICAL_RMM: 'TACTICAL_RMM',\n FLEET_MDM: 'FLEET_MDM',\n MESHCENTRAL: 'MESHCENTRAL',\n AUTHENTIK: 'AUTHENTIK',\n OPENFRAME: 'OPENFRAME',\n OPENFRAME_CHAT: 'OPENFRAME_CHAT',\n OPENFRAME_CLIENT: 'OPENFRAME_CLIENT',\n OSQUERY: 'OSQUERY',\n SYSTEM: 'SYSTEM'\n} as const\n\nexport type ToolType = (typeof ToolTypeValues)[keyof typeof ToolTypeValues]\n\n/**\n * Maps tool types to display labels\n */\nexport const toolLabels: Record<ToolType, string> = {\n TACTICAL_RMM: 'Tactical',\n FLEET_MDM: 'Fleet',\n MESHCENTRAL: 'MeshCentral',\n AUTHENTIK: 'Authentik',\n OPENFRAME: 'OpenFrame',\n OPENFRAME_CHAT: 'OpenFrame Chat',\n OPENFRAME_CLIENT: 'OpenFrame Client',\n OSQUERY: 'Osquery',\n SYSTEM: 'System'\n}\n","/**\n * Access Code Client Utilities — pure standalone functions.\n *\n * Endpoint paths are NOT hardcoded — every function takes an\n * `endpoints` argument. The React-side wrapper that binds them from\n * `EndpointsRuntimeContext` lives separately at\n * `hooks/use-access-code-integration.ts` (`useAccessCodeIntegration`).\n *\n * Keep this file **free of React imports** — it lives in the\n * server-safe `utils/index` tsup bundle. Any module-top-level call\n * into `createContext()` (which the runtime context file does) would\n * be pulled into the server bundle and crash SSR with\n * `createContext is not a function`.\n */\n\nimport {\n AccessCodeValidation,\n AccessCodeValidationResponse,\n AccessCodeConsumptionResponse\n} from '../types/access-code-cohorts';\n\n/** Endpoints required by the standalone client utilities. The\n * `useAccessCodeIntegration` hook (in `hooks/`) resolves these from\n * `EndpointsRuntimeContext.accessCode` automatically. */\nexport interface AccessCodeEndpoints {\n validateUrl: string\n consumeUrl: string\n}\n\n/**\n * Validate an access code for a given email\n *\n * @param email - User's email address\n * @param code - Access code to validate\n * @returns Promise with validation result\n *\n * @example\n * const result = await validateAccessCode('user@example.com', 'ABC123XY');\n * if (result.valid) {\n * // Allow user to proceed with registration\n * console.log(`Welcome to ${result.cohort_name}!`);\n * } else {\n * // Show error message\n * console.error(result.message);\n * }\n */\nexport async function validateAccessCode(\n email: string,\n code: string,\n endpoints: AccessCodeEndpoints,\n): Promise<AccessCodeValidationResponse> {\n try {\n const response = await fetch(endpoints.validateUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ email, code } as AccessCodeValidation),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error || 'Validation request failed');\n }\n\n return await response.json() as AccessCodeValidationResponse;\n } catch (error) {\n return {\n valid: false,\n message: error instanceof Error ? error.message : 'Validation failed',\n };\n }\n}\n\n/**\n * Consume an access code after successful registration\n *\n * Call this ONLY after the user has successfully completed registration.\n * This marks the code as used and prevents further usage.\n *\n * @param email - User's email address\n * @param code - Access code to consume\n * @returns Promise with consumption result\n *\n * @example\n * // After successful registration\n * const result = await consumeAccessCode('user@example.com', 'ABC123XY');\n * if (result.consumed) {\n * console.log('Access code consumed successfully');\n * } else {\n * console.warn('Failed to consume access code:', result.message);\n * }\n */\nexport async function consumeAccessCode(\n email: string,\n code: string,\n endpoints: AccessCodeEndpoints,\n): Promise<AccessCodeConsumptionResponse> {\n try {\n const response = await fetch(endpoints.consumeUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ email, code } as AccessCodeValidation),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error || 'Consumption request failed');\n }\n\n return await response.json() as AccessCodeConsumptionResponse;\n } catch (error) {\n return {\n success: false,\n consumed: false,\n message: error instanceof Error ? error.message : 'Consumption failed',\n };\n }\n}\n\n/**\n * Complete access code flow: validate then consume\n *\n * This is a convenience function that validates an access code and,\n * if valid, immediately consumes it. Use this when you want to\n * validate and consume in one step during registration.\n *\n * @param email - User's email address\n * @param code - Access code to validate and consume\n * @returns Promise with validation and consumption results\n *\n * @example\n * const result = await validateAndConsumeAccessCode('user@example.com', 'ABC123XY');\n * if (result.valid && result.consumed) {\n * // Registration successful\n * console.log(`Welcome to ${result.cohort_name}!`);\n * } else {\n * console.error(result.message);\n * }\n */\nexport async function validateAndConsumeAccessCode(\n email: string,\n code: string,\n endpoints: AccessCodeEndpoints,\n): Promise<AccessCodeValidationResponse & { consumed?: boolean }> {\n // First validate\n const validation = await validateAccessCode(email, code, endpoints);\n\n if (!validation.valid) {\n return validation;\n }\n\n // If valid, consume the code\n const consumption = await consumeAccessCode(email, code, endpoints);\n\n return {\n ...validation,\n consumed: consumption.consumed,\n message: consumption.consumed\n ? `Access granted for ${validation.cohort_name}`\n : consumption.message || validation.message,\n };\n}\n\n// `useAccessCodeIntegration` (the React-side wrapper) lives in\n// `hooks/use-access-code-integration.ts`. It binds the endpoints from\n// `EndpointsRuntimeContext` so React callers don't have to plumb URLs.\n","/**\n * `scrollElementIntoView` — canonical \"scroll an element to the top of the\n * viewport, account for sticky chrome, survive layout shifts\" helper.\n *\n * One shared implementation so every caller (the ticket drawer expand, the\n * hub's `useUnifiedNav` / `use-nav-link` hash scroll, doc-tree, delivery\n * `?focus=`, sticky-section-nav, …) inherits the SAME cancellation-proof\n * motion.\n *\n * WHY A SELF-DRIVEN rAF TWEEN INSTEAD OF `window.scrollTo({behavior:'smooth'})`:\n * the native smooth scroll is CANCELLABLE, and in real pages it gets cancelled\n * constantly:\n *\n * - Browser SCROLL ANCHORING: when content is inserted/removed above or\n * around the target (a collapsible drawer expanding, an async image\n * loading, a list re-rendering) the browser issues a synchronous scrollTop\n * correction to keep the anchored element stable. Per CSSOM-View \"perform a\n * scroll\" step 1 (\"abort any ongoing smooth scroll\"), that correction\n * ABORTS an in-flight native smooth scroll — so it lands as an instant jump.\n * Anchoring is suppressed when the scroll offset is 0, which is exactly why\n * a native smooth scroll appears to work the FIRST time (page at top) and\n * jumps on every repeat (page already scrolled). This was a multi-day\n * \"smooth only works once\" bug on the /tickets drawer.\n * - A second programmatic scroll on the same frame, or a `focus()` without\n * `{preventScroll:true}`, cancels it the same way.\n *\n * A tween that re-asserts the position with INSTANT writes every frame is\n * immune: there is no \"ongoing native smooth scroll\" for anchoring/focus to\n * abort, and any correction that lands between our frames is overwritten on the\n * next frame. We also RECOMPUTE the target each frame, so an element whose\n * final position is still settling (drawer still expanding, images loading)\n * is tracked to its resting place instead of animating to a stale pixel.\n *\n * Honors `prefers-reduced-motion` (jumps instantly) and cancels on genuine user\n * scroll intent (wheel / touch) so we never fight the user.\n *\n * WINDOW *OR* A SCROLLABLE ANCESTOR: the helper is not hard-wired to the window\n * scroller. It walks up from the target to the nearest ancestor that is an\n * actual scroll container (`overflow-y: auto | scroll | overlay` AND\n * `scrollHeight > clientHeight`) and drives THAT element; only when none exists\n * does it fall back to `window`. This is what makes it work inside app shells\n * that put page content in a fixed-height `<main class=\"overflow-y-auto\">`\n * (e.g. OpenFrame's `AppLayout`) where the document/window never scrolls — the\n * old window-only version was a silent no-op there. Note `overflow: clip` /\n * `hidden` are deliberately NOT treated as scroll containers, so a list wrapper\n * that uses `overflow-clip` only to round its corners still bubbles the scroll\n * up to the real container (matches the `<HelpCenterCard>` list intent).\n */\n\nexport interface ScrollElementIntoViewOptions {\n /** Pixels to subtract from the target element's `top` so it lands BELOW\n * sticky chrome. Defaults to 0. Pass `96` for the standard hub header. */\n headerOffset?: number\n /** `'smooth'` (default) runs the self-driven tween; `'instant'` / `'auto'`\n * jump in one synchronous write (deep-link land, programmatic focus moves). */\n behavior?: ScrollBehavior\n /** Optional adjustment applied to the computed pixel target each frame. The\n * callback receives the \"raw\" Y (`element.top + scrollY - headerOffset`) and\n * returns the FINAL target. Use when the caller knows about a layout shift\n * (e.g. a sibling drawer collapsing) the geometry can't yet reflect. */\n adjustTargetY?: (rawTargetY: number) => number\n /** Tween duration in ms (smooth only). Default 320. */\n durationMs?: number\n}\n\n/** Module-level handle to the in-flight tween so a new call (or a user\n * gesture) cancels the previous one — only ever one page-scroll animation at\n * a time. */\nlet activeRaf = 0\nlet teardownActive: (() => void) | null = null\n\nfunction cancelActiveScroll(): void {\n if (activeRaf) {\n cancelAnimationFrame(activeRaf)\n activeRaf = 0\n }\n if (teardownActive) {\n teardownActive()\n teardownActive = null\n }\n}\n\nconst easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3)\n\n/** Nearest ancestor that is a *real* scroll container, or `null` when the\n * window/document is the scroller. Only `auto | scroll | overlay` count —\n * `clip` / `hidden` are intentionally excluded (a wrapper using `overflow-clip`\n * purely to round corners must let the scroll bubble to the page). */\nfunction getScrollableAncestor(el: HTMLElement): HTMLElement | null {\n for (let node = el.parentElement; node; node = node.parentElement) {\n const overflowY = getComputedStyle(node).overflowY\n if (\n (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') &&\n node.scrollHeight > node.clientHeight\n ) {\n return node\n }\n }\n return null\n}\n\n/**\n * Scroll the page so `target` lands at the top of the viewport (below sticky\n * chrome via `headerOffset`). SSR-safe; `null`/`undefined` target is a no-op so\n * callers can pass refs without defensive branching.\n */\nexport function scrollElementIntoView(\n target: HTMLElement | null | undefined,\n options: ScrollElementIntoViewOptions = {},\n): void {\n if (typeof window === 'undefined' || !target) return\n const { headerOffset = 0, behavior = 'smooth', adjustTargetY, durationMs = 320 } = options\n\n // Pick the scroller ONCE: a fixed-height `<main overflow-y-auto>` shell scrolls\n // the element, a plain document scrolls the window. The choice can't change\n // mid-tween, so resolve it up front and route every read/write through it.\n const container = getScrollableAncestor(target)\n const readCurrent = (): number => (container ? container.scrollTop : window.scrollY)\n const writeTo = (y: number): void => {\n if (container) container.scrollTop = y\n else window.scrollTo(0, y)\n }\n\n // Target is recomputed every frame: the row's absolute position can move as\n // the page reflows (a sibling drawer collapsing) and the reachable max grows\n // as the just-opened drawer expands. Clamp to the LIVE max each frame.\n const computeTarget = (): number => {\n const raw = container\n ? container.scrollTop +\n (target.getBoundingClientRect().top - container.getBoundingClientRect().top) -\n headerOffset\n : target.getBoundingClientRect().top + window.scrollY - headerOffset\n const adjusted = adjustTargetY ? adjustTargetY(raw) : raw\n const maxScroll = container\n ? Math.max(0, container.scrollHeight - container.clientHeight)\n : Math.max(0, document.documentElement.scrollHeight - window.innerHeight)\n return Math.min(Math.max(0, adjusted), maxScroll)\n }\n\n // Any prior animation loses — one page scroll at a time.\n cancelActiveScroll()\n\n const prefersReduced =\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\n // Instant paths: a single synchronous write. No tween, no anchoring race.\n if (behavior === 'instant' || behavior === 'auto' || prefersReduced) {\n writeTo(computeTarget())\n return\n }\n\n // Smooth: self-driven tween with instant per-frame writes (anchoring-proof).\n let startY: number | null = null\n let startTime = 0\n\n // Bail the moment the user takes over with a real scroll gesture — we must\n // never fight them. (Not keydown: the ticket composer auto-focuses on open,\n // and typing there should not abort the scroll.)\n const onUserGesture = () => cancelActiveScroll()\n window.addEventListener('wheel', onUserGesture, { passive: true })\n window.addEventListener('touchmove', onUserGesture, { passive: true })\n teardownActive = () => {\n window.removeEventListener('wheel', onUserGesture)\n window.removeEventListener('touchmove', onUserGesture)\n }\n\n const step = (now: number) => {\n if (startY === null) {\n startY = readCurrent()\n startTime = now\n }\n const targetY = computeTarget()\n const t = Math.min(1, (now - startTime) / durationMs)\n const y = startY + (targetY - startY) * easeOutCubic(t)\n writeTo(y)\n if (t < 1) {\n activeRaf = requestAnimationFrame(step)\n } else {\n // Final exact write in case easing left a sub-pixel gap, then teardown.\n writeTo(computeTarget())\n activeRaf = 0\n if (teardownActive) {\n teardownActive()\n teardownActive = null\n }\n }\n }\n activeRaf = requestAnimationFrame(step)\n}\n","import { scrollElementIntoView } from './scroll-into-view'\n\n/** Pages with a section-nav STRIP on top of the global hub header\n * (dev-center roadmap/delivery/tickets, FAQ category-pill nav).\n * Anchor lands BELOW both layers. */\nexport const STICKY_HEADER_OFFSET_PX = 96\n\n/** Pages with only the global hub header (docs, blog, vendor detail).\n * Anchor lands BELOW the header bar. */\nexport const HUB_HEADER_OFFSET_PX = 80\n\n/**\n * Take only the FIRST hash segment from a fragment that may contain extra\n * `#` characters. `'' → ''`, `'#a' → '#a'`, `'#a#b' → '#a'`.\n *\n * No real DOM id contains `#`, so a multi-fragment hash is always a bug at\n * the composer site; `navigateSamePageHash` + `useScrollToHash` both call\n * this so URL bar and `getElementById` stay in sync.\n */\nexport function normalizeHashFragment(hash: string): string {\n if (!hash) return ''\n const second = hash.indexOf('#', 1)\n return second < 0 ? hash : hash.slice(0, second)\n}\n\nexport interface NavigateSamePageHashOptions {\n /** Pixels to subtract for sticky chrome. */\n headerOffset?: number\n /** `'push'` (default) — new history entry; `'replace'` — overwrite\n * current entry (use for TOC-style in-page navigators). */\n history?: 'push' | 'replace'\n}\n\n/**\n * Same-page hash navigation primitive: pushState + synthetic `hashchange`\n * + anchoring-proof smooth scroll. Replaces `router.push` for hash CTAs\n * (Next.js suppresses smooth-scroll during navigation; `router.push` on\n * an exact-URL match is a no-op). Returns `true` when the helper claimed\n * the nav (same pathname + search); `false` for cross-page targets so\n * callers fall through to `router.push`.\n *\n * `target` accepts an origin-stripped path (`/x#anchor`) or a bare hash\n * (`#anchor`); bare-hash callers don't need to reconstruct `pathname +\n * search` themselves.\n */\nexport function navigateSamePageHash(\n target: string,\n options: NavigateSamePageHashOptions = {},\n): boolean {\n if (typeof window === 'undefined') return false\n const { headerOffset = 0, history: historyMode = 'push' } = options\n const normalizedTarget =\n target.startsWith('#')\n ? window.location.pathname + window.location.search + target\n : target\n // `new URL(absoluteUrl, base)` ignores `base` per RFC 3986; an absolute\n // cross-origin target sharing pathname/search would otherwise pass the\n // check below and trip pushState's same-origin enforcement. Parse with\n // an explicit base so malformed inputs cleanly fall through.\n let url: URL\n try {\n url = new URL(normalizedTarget, window.location.href)\n } catch {\n return false\n }\n if (\n url.origin !== window.location.origin ||\n url.pathname !== window.location.pathname ||\n url.search !== window.location.search\n ) {\n return false\n }\n const current = window.location.pathname + window.location.search + window.location.hash\n // Heal a malformed multi-fragment hash so the URL bar is clean and\n // `getElementById` resolves. Dev-warn fingers the upstream composer.\n const normalizedHash = normalizeHashFragment(url.hash)\n if (process.env.NODE_ENV === 'development' && normalizedHash !== url.hash) {\n // eslint-disable-next-line no-console\n console.warn(\n `[navigateSamePageHash] malformed fragment \"${url.hash}\" → normalizing to \"${normalizedHash}\". Fix the upstream composer.`,\n )\n }\n const next = url.pathname + url.search + normalizedHash\n const id = normalizedHash && normalizedHash !== '#' ? normalizedHash.slice(1) : ''\n // Hash-less targets are only ours on an EXACT URL re-click.\n if (!id && next !== current) return false\n if (next !== current) {\n const oldURL = window.location.href\n if (historyMode === 'replace') {\n window.history.replaceState(null, '', next)\n } else {\n window.history.pushState(null, '', next)\n }\n // Synthetic `hashchange` — `pushState` doesn't fire it (HTML spec),\n // so URL-hash-bound listeners (FAQ auto-expand, etc.) wouldn't react.\n window.dispatchEvent(new HashChangeEvent('hashchange', {\n oldURL,\n newURL: window.location.href,\n }))\n }\n const el = id ? document.getElementById(id) : null\n if (id && !el && process.env.NODE_ENV === 'development') {\n // eslint-disable-next-line no-console\n console.warn(\n `[navigateSamePageHash] anchor \"#${id}\" not found — scrolling to top.`,\n )\n }\n // Missing anchor → tween to page top. `documentElement` is at 0 by\n // definition, so one tween covers both branches.\n scrollElementIntoView(el ?? document.documentElement, {\n behavior: 'smooth',\n headerOffset,\n })\n return true\n}\n","/**\n * Humanity signals — invisible bot-protection primitives shared by the lib's\n * public forms (client) and the hub's per-route `verifyHuman` gate (server).\n *\n * PURE + React-free on purpose: this module is a tsup SERVER entry (no\n * \"use client\" banner) so the hub can import it server-side without pulling a\n * client-reference boundary — same pattern as `schemas/contact-schema` and\n * `components/features/mux-origins`.\n *\n * Two origin-independent signals travel in the POST body: a honeypot (a hidden\n * field real users never fill) and timing (ms from form mount to submit).\n * `evaluateHumanitySignals` is the SINGLE source of truth for the block/allow\n * decision — the hub imports + calls it rather than re-implementing the rules.\n */\n\n/** Hidden honeypot field name. Innocuous + autofill-resistant (deliberately NOT name/email). */\nexport const HONEYPOT_FIELD = 'contact_url_confirm'\n/** Client-measured ms between form mount and submit. */\nexport const ELAPSED_MS_FIELD = 'form_elapsed_ms'\n/** Default minimum fill time (ms). A submit faster than this is treated as a bot. */\nexport const DEFAULT_MIN_FILL_MS = 700\n\n/** Keyed wire object produced by `useHumanitySignals().getSignals()` and spread into the POST body. */\nexport type HumanitySignals = Record<string, string | number>\n\n/** Result of {@link evaluateHumanitySignals}. */\nexport type HumanityVerdict = { ok: true } | { ok: false; reason: 'honeypot' | 'too_fast' }\n\n/** Tolerant reader — never throws; missing/garbage timing → null. */\nexport function extractHumanitySignals(body: unknown): { honeypot: string; elapsedMs: number | null } {\n const b = (body ?? {}) as Record<string, unknown>\n const rawHp = b[HONEYPOT_FIELD]\n // A legit client always sends a STRING here (getSignals → ref.value ?? ''),\n // so ANY present non-string value is a bot filling the decoy with a non-string\n // to dodge the empty-check — coerce to a (non-empty) string so it still trips.\n // null/undefined → '' = the correct \"field absent / unfilled\" allow case.\n const honeypot = rawHp == null ? '' : String(rawHp)\n const rawMs = b[ELAPSED_MS_FIELD]\n const elapsedMs = typeof rawMs === 'number' && Number.isFinite(rawMs) ? rawMs : null\n return { honeypot, elapsedMs }\n}\n\n/**\n * SINGLE decision fn for honeypot + timing (the hub's `verifyHuman` imports + calls this):\n * - honeypot non-empty → bot (real users never fill the off-screen field)\n * - elapsed below `minFillMs` → bot (humans take time; a MISSING timing value never blocks)\n */\nexport function evaluateHumanitySignals(body: unknown, opts: { minFillMs: number }): HumanityVerdict {\n const { honeypot, elapsedMs } = extractHumanitySignals(body)\n if (honeypot.trim() !== '') return { ok: false, reason: 'honeypot' }\n if (elapsedMs !== null && elapsedMs < opts.minFillMs) return { ok: false, reason: 'too_fast' }\n return { ok: true }\n}\n\n/** Parse a comma-separated env string → trimmed, non-empty entries (undefined → []). */\nexport const splitCsvEnv = (s?: string): string[] =>\n s?.split(',').map((t) => t.trim()).filter(Boolean) ?? []\n"]}
|