@ekanos/sdk 0.1.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 +21 -0
- package/README.md +78 -0
- package/dist/components/base-activation-dialog.d.ts +41 -0
- package/dist/components/base-activation-dialog.js +41 -0
- package/dist/components/base-activation-dialog.js.map +1 -0
- package/dist/components/base-activation-form.d.ts +36 -0
- package/dist/components/base-activation-form.js +21 -0
- package/dist/components/base-activation-form.js.map +1 -0
- package/dist/components/base-marketplace-tile.d.ts +34 -0
- package/dist/components/base-marketplace-tile.js +40 -0
- package/dist/components/base-marketplace-tile.js.map +1 -0
- package/dist/components/current-return-path.d.ts +19 -0
- package/dist/components/current-return-path.js +26 -0
- package/dist/components/current-return-path.js.map +1 -0
- package/dist/components/index.d.ts +40 -0
- package/dist/components/index.js +38 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/oauth-activation-form.d.ts +50 -0
- package/dist/components/oauth-activation-form.js +119 -0
- package/dist/components/oauth-activation-form.js.map +1 -0
- package/dist/components/widgets/ai-prompt-chip.d.ts +31 -0
- package/dist/components/widgets/ai-prompt-chip.js +38 -0
- package/dist/components/widgets/ai-prompt-chip.js.map +1 -0
- package/dist/components/widgets/ask-icon.d.ts +3 -0
- package/dist/components/widgets/ask-icon.js +5 -0
- package/dist/components/widgets/ask-icon.js.map +1 -0
- package/dist/components/widgets/use-animated-height.d.ts +12 -0
- package/dist/components/widgets/use-animated-height.js +39 -0
- package/dist/components/widgets/use-animated-height.js.map +1 -0
- package/dist/components/widgets/widget-ask-bar.d.ts +18 -0
- package/dist/components/widgets/widget-ask-bar.js +62 -0
- package/dist/components/widgets/widget-ask-bar.js.map +1 -0
- package/dist/components/widgets/widget-context.d.ts +38 -0
- package/dist/components/widgets/widget-context.js +4 -0
- package/dist/components/widgets/widget-context.js.map +1 -0
- package/dist/components/widgets/widget-state-components.d.ts +5 -0
- package/dist/components/widgets/widget-state-components.js +19 -0
- package/dist/components/widgets/widget-state-components.js.map +1 -0
- package/dist/components/widgets/widget.d.ts +91 -0
- package/dist/components/widgets/widget.js +205 -0
- package/dist/components/widgets/widget.js.map +1 -0
- package/dist/context/egress.d.ts +30 -0
- package/dist/context/egress.js +81 -0
- package/dist/context/egress.js.map +1 -0
- package/dist/context/errors.d.ts +57 -0
- package/dist/context/errors.js +78 -0
- package/dist/context/errors.js.map +1 -0
- package/dist/context/index.d.ts +19 -0
- package/dist/context/index.js +20 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/require-context.d.ts +26 -0
- package/dist/context/require-context.js +36 -0
- package/dist/context/require-context.js.map +1 -0
- package/dist/context/storage-validation.d.ts +71 -0
- package/dist/context/storage-validation.js +132 -0
- package/dist/context/storage-validation.js.map +1 -0
- package/dist/context/types.d.ts +11 -0
- package/dist/context/types.js +2 -0
- package/dist/context/types.js.map +1 -0
- package/dist/hooks/activation-actions-context.d.ts +80 -0
- package/dist/hooks/activation-actions-context.js +31 -0
- package/dist/hooks/activation-actions-context.js.map +1 -0
- package/dist/hooks/ask-assistant-context.d.ts +32 -0
- package/dist/hooks/ask-assistant-context.js +56 -0
- package/dist/hooks/ask-assistant-context.js.map +1 -0
- package/dist/hooks/fetch-integration-storage.d.ts +48 -0
- package/dist/hooks/fetch-integration-storage.js +68 -0
- package/dist/hooks/fetch-integration-storage.js.map +1 -0
- package/dist/hooks/index.d.ts +28 -0
- package/dist/hooks/index.js +32 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/use-activate-integration.d.ts +15 -0
- package/dist/hooks/use-activate-integration.js +61 -0
- package/dist/hooks/use-activate-integration.js.map +1 -0
- package/dist/hooks/use-oauth-connection-status.d.ts +360 -0
- package/dist/hooks/use-oauth-connection-status.js +108 -0
- package/dist/hooks/use-oauth-connection-status.js.map +1 -0
- package/dist/hooks/use-widget-events.d.ts +20 -0
- package/dist/hooks/use-widget-events.js +51 -0
- package/dist/hooks/use-widget-events.js.map +1 -0
- package/dist/hooks/widget-event-context.d.ts +22 -0
- package/dist/hooks/widget-event-context.js +75 -0
- package/dist/hooks/widget-event-context.js.map +1 -0
- package/dist/hooks/widget-event-types.d.ts +55 -0
- package/dist/hooks/widget-event-types.js +9 -0
- package/dist/hooks/widget-event-types.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/cron.d.ts +29 -0
- package/dist/integration/cron.js +102 -0
- package/dist/integration/cron.js.map +1 -0
- package/dist/integration/define-integration.d.ts +28 -0
- package/dist/integration/define-integration.js +41 -0
- package/dist/integration/define-integration.js.map +1 -0
- package/dist/integration/event-surfaces.d.ts +15 -0
- package/dist/integration/event-surfaces.js +45 -0
- package/dist/integration/event-surfaces.js.map +1 -0
- package/dist/integration/index.d.ts +20 -0
- package/dist/integration/index.js +23 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/integration/types.d.ts +7 -0
- package/dist/integration/types.js +2 -0
- package/dist/integration/types.js.map +1 -0
- package/dist/mcp/guards.d.ts +40 -0
- package/dist/mcp/guards.js +99 -0
- package/dist/mcp/guards.js.map +1 -0
- package/dist/mcp/index.d.ts +22 -0
- package/dist/mcp/index.js +22 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/ownership.d.ts +74 -0
- package/dist/mcp/ownership.js +83 -0
- package/dist/mcp/ownership.js.map +1 -0
- package/dist/mcp/types.d.ts +145 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.js +19 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/invoke.d.ts +77 -0
- package/dist/testing/invoke.js +83 -0
- package/dist/testing/invoke.js.map +1 -0
- package/dist/testing/mock-context.d.ts +79 -0
- package/dist/testing/mock-context.js +206 -0
- package/dist/testing/mock-context.js.map +1 -0
- package/dist/types/icon.d.ts +12 -0
- package/dist/types/icon.js +2 -0
- package/dist/types/icon.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/integration.d.ts +258 -0
- package/dist/types/integration.js +2 -0
- package/dist/types/integration.js.map +1 -0
- package/dist/types/widget-ask-context.d.ts +36 -0
- package/dist/types/widget-ask-context.js +2 -0
- package/dist/types/widget-ask-context.js.map +1 -0
- package/dist/types/workspace-target.d.ts +33 -0
- package/dist/types/workspace-target.js +2 -0
- package/dist/types/workspace-target.js.map +1 -0
- package/package.json +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vastly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# @ekanos/sdk
|
|
2
|
+
|
|
3
|
+
The official SDK for building Ekanos integrations.
|
|
4
|
+
|
|
5
|
+
## Entrypoints
|
|
6
|
+
|
|
7
|
+
| Import | Runs on | Contents |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `@ekanos/sdk` | anywhere | Types only — the integration contract (`IntegrationMetadata`, component prop types, activation/health/data types) and the capability context types (`IntegrationContext`, `ScopedStore`, `StorageSchemas`, …) |
|
|
10
|
+
| `@ekanos/sdk/components` | client | `Widget` compound API + `WidgetContext`, `BaseActivationForm`, `OAuthActivationForm`, `BaseMarketplaceTile`, `AiPromptChip` — implemented here (the inversion); UI primitives from `@ekanos/ui` |
|
|
11
|
+
| `@ekanos/sdk/hooks` | client | `useActivateIntegration`/`useDeactivateIntegration` (host-injected actions via `IntegrationActivationProvider`), `useOAuthConnectionStatus`, widget event bus, `useAskAssistant` |
|
|
12
|
+
| `@ekanos/sdk/mcp` | server | `ToolModule`/`ToolContext` contract + membership/ownership guards — implemented here |
|
|
13
|
+
| `@ekanos/sdk/context` | anywhere (isomorphic, dependency-pure) | Capability-context shared runtime: `EgressDeniedError`, `SecretAccessError`, `StorageValidationError`, the `isEgressAllowed` matcher, the storage validators — one implementation reused by the production wrapper and the mock |
|
|
14
|
+
| `@ekanos/sdk/testing` | anywhere (tests) | `createMockContext()` — the in-memory `IntegrationContext` harness (schema-validated stores, tiered secrets, allowlist-enforcing fetch stub, capturing logger) |
|
|
15
|
+
| `@ekanos/sdk/integration` | anywhere (isomorphic, dependency-pure) | The partner authoring contract: `defineIntegration()` (validate + deep-freeze, storage schemas per ruling 1, https-origin egress per ruling 3, security classifications only as a `proposes` block), `validateIntegrationDefinitions()` (cross-definition slug/widget/tool collision check), and `IntegrationDefinitionSchema` for the gate/dev-mcp validators |
|
|
16
|
+
|
|
17
|
+
`/context`, `/testing`, and `/integration` import nothing from `@kit/*` and
|
|
18
|
+
carry no `server-only` marker — they are self-contained by construction and
|
|
19
|
+
the pack test typechecks all three in a clean room (alongside the root). The
|
|
20
|
+
capability context spec is
|
|
21
|
+
[`docs/devex/capability-context-proposal.md`](../../docs/devex/capability-context-proposal.md).
|
|
22
|
+
Partners never extend `BaseIntegration` — `defineIntegration()` is the v1
|
|
23
|
+
contract, and the host adapts the definition via
|
|
24
|
+
`registerPartnerIntegration` in `@kit/integrations-core` (typed there as a
|
|
25
|
+
structural twin, held in parity by
|
|
26
|
+
`src/integration/__tests__/partner-definition-parity.test-d.ts`).
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
- The root entrypoint is types-only and must stay importable from client code.
|
|
31
|
+
Never add a value export to it.
|
|
32
|
+
- Additions anywhere require a second consumer and an entry in
|
|
33
|
+
[`docs/devex/sdk-export-map.md`](../../docs/devex/sdk-export-map.md) — the
|
|
34
|
+
export map is the source of truth for what is public and why.
|
|
35
|
+
- The inversion landed: this package OWNS the `/components`, `/hooks`, and
|
|
36
|
+
`/mcp` implementations and `@kit/integrations-core` re-exports them. Never
|
|
37
|
+
import `@kit/*` from SDK source — UI comes from `@ekanos/ui`, host services
|
|
38
|
+
arrive through injection seams (`IntegrationActivationProvider`,
|
|
39
|
+
`ToolContext`). There is no `/server` entrypoint: first-party transitional
|
|
40
|
+
accessors live in `@kit/integrations-core/server` (core depends on the SDK
|
|
41
|
+
now, so the SDK cannot re-export core without a package cycle).
|
|
42
|
+
|
|
43
|
+
## Publish pipeline
|
|
44
|
+
|
|
45
|
+
Workspace consumers resolve raw `src/*.ts` via `exports`; the published
|
|
46
|
+
artifact is compiled ESM + declarations in `dist/`, wired through
|
|
47
|
+
`publishConfig` (applied by `pnpm pack`/`pnpm publish`). The tarball ships
|
|
48
|
+
`dist` + `README.md` only.
|
|
49
|
+
|
|
50
|
+
**Runtime dependencies + publish order.** The SDK depends on
|
|
51
|
+
`@ekanos/integration-schema` (the dependency-pure canonical schema — one
|
|
52
|
+
contract shared with `@kit/integrations-core`) and `@ekanos/ui` (the
|
|
53
|
+
published slice of the host design system that `/components` renders
|
|
54
|
+
through). Publish **schema and ui first**, then the SDK: the packed SDK
|
|
55
|
+
declares both as normal dependencies, so they must already be on the registry
|
|
56
|
+
for a fresh `npm install @ekanos/sdk` to resolve. The `pack:test` proves this
|
|
57
|
+
offline by packing all three, installing ONLY the SDK tarball (schema + ui
|
|
58
|
+
pulled transitively) and executing a real
|
|
59
|
+
`import("@ekanos/sdk/integration")`.
|
|
60
|
+
|
|
61
|
+
| Script | What it does |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `pnpm --filter @ekanos/sdk build` | `tsc -p tsconfig.build.json` → `dist/` (ESM + `.d.ts` + maps; `__tests__` excluded) |
|
|
64
|
+
| `pnpm --filter @ekanos/sdk test` | Vitest over `src/**/__tests__` — matcher semantics, storage schema enforcement, secret tier rules, mock recording surfaces |
|
|
65
|
+
| `pnpm --filter @ekanos/sdk api-report` | Regenerates [`api-report.md`](./api-report.md), the checked-in snapshot of every entrypoint's exports |
|
|
66
|
+
| `pnpm --filter @ekanos/sdk api-report:check` | Fails on drift between the surface and `api-report.md` |
|
|
67
|
+
| `pnpm --filter @ekanos/sdk pack:test` | Clean-room test: pack the SDK (and `@ekanos/integration-schema` + `@ekanos/ui`), install ONLY the SDK tarball outside the workspace with the declared peers, typecheck a consumer of EVERY published entrypoint (`skipLibCheck: false`), esbuild-bundle the client and server graphs, grep dist for `@kit/*` import specifiers, and EXECUTE `import("@ekanos/sdk/integration")` in real Node |
|
|
68
|
+
|
|
69
|
+
The pack test was the forcing function for the SDK inversion
|
|
70
|
+
([export map](../../docs/devex/sdk-export-map.md), review outcome 6), and the
|
|
71
|
+
inversion landed: every published entrypoint (root, `/components`, `/hooks`,
|
|
72
|
+
`/mcp`, `/context`, `/testing`, `/integration`) is typechecked in the clean
|
|
73
|
+
room and must be GREEN — no skips remain. Nothing is a v1 contract until it
|
|
74
|
+
passes.
|
|
75
|
+
|
|
76
|
+
Publishing itself is a deliberate human step: flip `"private": true` and run
|
|
77
|
+
`pnpm publish` (`publishConfig.access` is already `public`, version stays
|
|
78
|
+
`0.0.0` until then).
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
interface BaseActivationDialogProps {
|
|
3
|
+
isOpen: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
title: string;
|
|
6
|
+
description: string;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
isLoading?: boolean;
|
|
9
|
+
error?: string | null;
|
|
10
|
+
success?: boolean;
|
|
11
|
+
successTitle?: string;
|
|
12
|
+
successMessage?: string;
|
|
13
|
+
onSuccessClose?: () => void;
|
|
14
|
+
maxWidth?: string;
|
|
15
|
+
/** @deprecated Use `ActivationInline` instead for inline rendering */
|
|
16
|
+
inline?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function BaseActivationDialog({ isOpen, onClose, title, description, children, isLoading, error, success, successTitle, successMessage, onSuccessClose, maxWidth, inline, }: BaseActivationDialogProps): import("react").JSX.Element;
|
|
19
|
+
export declare function ActivationInline({ isLoading, error, success, successTitle, successMessage, onClose, children, }: {
|
|
20
|
+
isLoading?: boolean;
|
|
21
|
+
error?: string | null;
|
|
22
|
+
success?: boolean;
|
|
23
|
+
successTitle?: string;
|
|
24
|
+
successMessage?: string;
|
|
25
|
+
onClose: () => void;
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
28
|
+
export declare function ActivationDialog({ isOpen, onClose, title, description, children, isLoading, error, success, successTitle, successMessage, maxWidth, }: {
|
|
29
|
+
isOpen: boolean;
|
|
30
|
+
onClose: () => void;
|
|
31
|
+
title: string;
|
|
32
|
+
description: string;
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
isLoading?: boolean;
|
|
35
|
+
error?: string | null;
|
|
36
|
+
success?: boolean;
|
|
37
|
+
successTitle?: string;
|
|
38
|
+
successMessage?: string;
|
|
39
|
+
maxWidth?: string;
|
|
40
|
+
}): import("react").JSX.Element;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Alert, AlertDescription } from '@ekanos/ui/alert';
|
|
4
|
+
import { Button } from '@ekanos/ui/button';
|
|
5
|
+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@ekanos/ui/dialog';
|
|
6
|
+
import { Icon } from '@ekanos/ui/icon';
|
|
7
|
+
import { If } from '@ekanos/ui/if';
|
|
8
|
+
import { Separator } from '@ekanos/ui/separator';
|
|
9
|
+
import { Trans } from '@ekanos/ui/trans';
|
|
10
|
+
import { cn } from '@ekanos/ui/utils';
|
|
11
|
+
function SuccessContent({ successTitle = 'Integration Activated!', successMessage = 'Your integration is now active and ready to use.', onClose, showSeparator = false, }) {
|
|
12
|
+
return (_jsxs("div", { className: "flex flex-col items-center space-y-4 py-6", children: [_jsx("div", { className: "bg-success/10 flex h-16 w-16 items-center justify-center rounded-full", children: _jsx(Icon, { name: "fa-light fa-circle-check", className: "text-success h-8 w-8" }) }), _jsxs("div", { className: "text-center", children: [_jsx("h3", { className: "text-lg font-semibold", children: successTitle }), _jsx("p", { className: "text-muted-foreground mt-1 text-sm", children: successMessage })] }), showSeparator && _jsx(Separator, {}), _jsxs("div", { className: "flex gap-2", children: [showSeparator && (_jsx(Button, { variant: "outline", onClick: onClose, children: _jsx(Trans, { i18nKey: "common:stayOnMarketplace", defaults: "Stay on Marketplace" }) })), _jsx(Button, { onClick: onClose, children: _jsx(Trans, { i18nKey: "common:continue", defaults: "Continue" }) })] })] }));
|
|
13
|
+
}
|
|
14
|
+
function ErrorAlert({ error }) {
|
|
15
|
+
return (_jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: error }) }));
|
|
16
|
+
}
|
|
17
|
+
function LoadingOverlay() {
|
|
18
|
+
return (_jsx("div", { className: "bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center", children: _jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(Icon, { name: "fa-light fa-spinner", className: "text-primary h-8 w-8 animate-spin" }), _jsx("span", { className: "text-muted-foreground text-sm", children: "Loading..." })] }) }));
|
|
19
|
+
}
|
|
20
|
+
export function BaseActivationDialog({ isOpen, onClose, title, description, children, isLoading = false, error = null, success = false, successTitle, successMessage, onSuccessClose, maxWidth = 'sm:max-w-[600px]', inline = false, }) {
|
|
21
|
+
const handleClose = () => {
|
|
22
|
+
if (success && onSuccessClose) {
|
|
23
|
+
onSuccessClose();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
onClose();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
if (inline) {
|
|
30
|
+
return (_jsx(ActivationInline, { isLoading: isLoading, error: error, success: success, successTitle: successTitle, successMessage: successMessage, onClose: handleClose, children: children }));
|
|
31
|
+
}
|
|
32
|
+
return (_jsx(ActivationDialog, { isOpen: isOpen, onClose: handleClose, title: title, description: description, isLoading: isLoading, error: error, success: success, successTitle: successTitle, successMessage: successMessage, maxWidth: maxWidth, children: children }));
|
|
33
|
+
}
|
|
34
|
+
// --- Explicit Variants ---
|
|
35
|
+
export function ActivationInline({ isLoading = false, error = null, success = false, successTitle, successMessage, onClose, children, }) {
|
|
36
|
+
return (_jsxs("div", { className: cn('space-y-4', isLoading && 'pointer-events-none opacity-60'), "aria-busy": isLoading, children: [_jsx(If, { condition: !!error, children: _jsx(ErrorAlert, { error: error }) }), _jsx(If, { condition: success, fallback: children, children: _jsx(SuccessContent, { successTitle: successTitle, successMessage: successMessage, onClose: onClose }) })] }));
|
|
37
|
+
}
|
|
38
|
+
export function ActivationDialog({ isOpen, onClose, title, description, children, isLoading = false, error = null, success = false, successTitle, successMessage, maxWidth = 'sm:max-w-[600px]', }) {
|
|
39
|
+
return (_jsx(Dialog, { open: isOpen, onOpenChange: (open) => !open && onClose(), children: _jsxs(DialogContent, { className: `${maxWidth} max-h-[80vh] overflow-y-auto`, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title }), _jsx(DialogDescription, { children: description })] }), _jsx(If, { condition: !!error, children: _jsx(ErrorAlert, { error: error }) }), _jsx(If, { condition: success, fallback: children, children: _jsx(SuccessContent, { successTitle: successTitle, successMessage: successMessage, onClose: onClose, showSeparator: true }) }), isLoading && _jsx(LoadingOverlay, {})] }) }));
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=base-activation-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-activation-dialog.js","sourceRoot":"","sources":["../../src/components/base-activation-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { ReactNode } from 'react';\n\nimport { Alert, AlertDescription } from '@ekanos/ui/alert';\nimport { Button } from '@ekanos/ui/button';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@ekanos/ui/dialog';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { Separator } from '@ekanos/ui/separator';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\ninterface BaseActivationDialogProps {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n maxWidth?: string;\n /** @deprecated Use `ActivationInline` instead for inline rendering */\n inline?: boolean;\n}\n\nfunction SuccessContent({\n successTitle = 'Integration Activated!',\n successMessage = 'Your integration is now active and ready to use.',\n onClose,\n showSeparator = false,\n}: {\n successTitle?: string;\n successMessage?: string;\n onClose: () => void;\n showSeparator?: boolean;\n}) {\n return (\n <div className=\"flex flex-col items-center space-y-4 py-6\">\n <div className=\"bg-success/10 flex h-16 w-16 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-check\"\n className=\"text-success h-8 w-8\"\n />\n </div>\n <div className=\"text-center\">\n <h3 className=\"text-lg font-semibold\">{successTitle}</h3>\n <p className=\"text-muted-foreground mt-1 text-sm\">{successMessage}</p>\n </div>\n {showSeparator && <Separator />}\n <div className=\"flex gap-2\">\n {showSeparator && (\n <Button variant=\"outline\" onClick={onClose}>\n <Trans\n i18nKey=\"common:stayOnMarketplace\"\n defaults=\"Stay on Marketplace\"\n />\n </Button>\n )}\n <Button onClick={onClose}>\n <Trans i18nKey=\"common:continue\" defaults=\"Continue\" />\n </Button>\n </div>\n </div>\n );\n}\n\nfunction ErrorAlert({ error }: { error: string }) {\n return (\n <Alert variant=\"destructive\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n );\n}\n\nfunction LoadingOverlay() {\n return (\n <div className=\"bg-background/80 fixed inset-0 z-[1000] flex items-center justify-center\">\n <div className=\"flex flex-col items-center gap-2\">\n <Icon\n name=\"fa-light fa-spinner\"\n className=\"text-primary h-8 w-8 animate-spin\"\n />\n <span className=\"text-muted-foreground text-sm\">Loading...</span>\n </div>\n </div>\n );\n}\n\nexport function BaseActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n maxWidth = 'sm:max-w-[600px]',\n inline = false,\n}: BaseActivationDialogProps) {\n const handleClose = () => {\n if (success && onSuccessClose) {\n onSuccessClose();\n } else {\n onClose();\n }\n };\n\n if (inline) {\n return (\n <ActivationInline\n isLoading={isLoading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={handleClose}\n >\n {children}\n </ActivationInline>\n );\n }\n\n return (\n <ActivationDialog\n isOpen={isOpen}\n onClose={handleClose}\n title={title}\n description={description}\n isLoading={isLoading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n maxWidth={maxWidth}\n >\n {children}\n </ActivationDialog>\n );\n}\n\n// --- Explicit Variants ---\n\nexport function ActivationInline({\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onClose,\n children,\n}: {\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onClose: () => void;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn('space-y-4', isLoading && 'pointer-events-none opacity-60')}\n aria-busy={isLoading}\n >\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n <If condition={success} fallback={children}>\n <SuccessContent\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={onClose}\n />\n </If>\n </div>\n );\n}\n\nexport function ActivationDialog({\n isOpen,\n onClose,\n title,\n description,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n maxWidth = 'sm:max-w-[600px]',\n}: {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n maxWidth?: string;\n}) {\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>\n <DialogContent className={`${maxWidth} max-h-[80vh] overflow-y-auto`}>\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n\n <If condition={success} fallback={children}>\n <SuccessContent\n successTitle={successTitle}\n successMessage={successMessage}\n onClose={onClose}\n showSeparator\n />\n </If>\n\n {isLoading && <LoadingOverlay />}\n </DialogContent>\n </Dialog>\n );\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
|
3
|
+
export interface BaseActivationFormProps<TFieldValues extends FieldValues = FieldValues> {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
/**
|
|
9
|
+
* Generic rather than `UseFormReturn<any>`: with current react-hook-form
|
|
10
|
+
* typings a concrete `UseFormReturn<T>` is NOT assignable to
|
|
11
|
+
* `UseFormReturn<any>` (the `control._options.validate` parameter is
|
|
12
|
+
* checked contravariantly), which broke every out-of-repo consumer — the
|
|
13
|
+
* clean-room pack test caught it. Inference from the `form` prop keeps
|
|
14
|
+
* every call site source-compatible.
|
|
15
|
+
*/
|
|
16
|
+
form: UseFormReturn<TFieldValues, any, any>;
|
|
17
|
+
onSubmit: (data: TFieldValues) => Promise<void> | void;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
isLoading?: boolean;
|
|
20
|
+
error?: string | null;
|
|
21
|
+
success?: boolean;
|
|
22
|
+
successTitle?: string;
|
|
23
|
+
successMessage?: string;
|
|
24
|
+
onSuccessClose?: () => void;
|
|
25
|
+
submitButtonText?: string;
|
|
26
|
+
submitButtonLoadingText?: string;
|
|
27
|
+
submitButtonDisabled?: boolean;
|
|
28
|
+
/** @deprecated No longer needed — cancel button is hidden by default */
|
|
29
|
+
showCancelButton?: boolean;
|
|
30
|
+
/** @deprecated No longer needed — submit button is centered by default */
|
|
31
|
+
centerSubmitButton?: boolean;
|
|
32
|
+
maxWidth?: string;
|
|
33
|
+
/** Render inline instead of in a dialog */
|
|
34
|
+
inline?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function BaseActivationForm<TFieldValues extends FieldValues = FieldValues>({ isOpen, onClose, title, description, form, onSubmit, children, isLoading, error, success, successTitle, successMessage, onSuccessClose, submitButtonText, submitButtonLoadingText, submitButtonDisabled, maxWidth, inline, }: BaseActivationFormProps<TFieldValues>): import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useTransition } from 'react';
|
|
4
|
+
import { Button } from '@ekanos/ui/button';
|
|
5
|
+
import { Form } from '@ekanos/ui/form';
|
|
6
|
+
import { Icon } from '@ekanos/ui/icon';
|
|
7
|
+
import { BaseActivationDialog } from './base-activation-dialog.js';
|
|
8
|
+
export function BaseActivationForm({ isOpen, onClose, title, description, form, onSubmit, children, isLoading = false, error = null, success = false, successTitle, successMessage, onSuccessClose, submitButtonText = 'Activate Integration', submitButtonLoadingText, submitButtonDisabled = false, maxWidth, inline = false, }) {
|
|
9
|
+
const [pending, startTransition] = useTransition();
|
|
10
|
+
const loading = pending || isLoading;
|
|
11
|
+
const handleSubmit = (data) => {
|
|
12
|
+
startTransition(async () => {
|
|
13
|
+
await onSubmit(data);
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
const formContent = (_jsx(Form, Object.assign({}, form, { children: _jsxs("form", { onSubmit: form.handleSubmit(handleSubmit), className: "space-y-6", children: [children, _jsx("div", { className: "flex justify-center pt-4", children: _jsxs(Button, { type: "submit", disabled: loading || submitButtonDisabled, children: [loading && (_jsx(Icon, { name: "fa-light fa-spinner", className: "mr-2 h-4 w-4 animate-spin" })), loading && submitButtonLoadingText
|
|
17
|
+
? submitButtonLoadingText
|
|
18
|
+
: submitButtonText] }) })] }) })));
|
|
19
|
+
return (_jsx(BaseActivationDialog, { isOpen: isOpen, onClose: onClose, title: title, description: description, isLoading: loading, error: error, success: success, successTitle: successTitle, successMessage: successMessage, onSuccessClose: onSuccessClose, maxWidth: maxWidth, inline: inline, children: formContent }));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=base-activation-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-activation-form.js","sourceRoot":"","sources":["../../src/components/base-activation-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAa,aAAa,EAAE,MAAM,OAAO,CAAC;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAGvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAuChE,MAAM,UAAU,kBAAkB,CAEhC,EACA,MAAM,EACN,OAAO,EACP,KAAK,EACL,WAAW,EACX,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,SAAS,GAAG,KAAK,EACjB,KAAK,GAAG,IAAI,EACZ,OAAO,GAAG,KAAK,EACf,YAAY,EACZ,cAAc,EACd,cAAc,EACd,gBAAgB,GAAG,sBAAsB,EACzC,uBAAuB,EACvB,oBAAoB,GAAG,KAAK,EAC5B,QAAQ,EACR,MAAM,GAAG,KAAK,GACwB;IACtC,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,aAAa,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,OAAO,IAAI,SAAS,CAAC;IAErC,MAAM,YAAY,GAAG,CAAC,IAAkB,EAAE,EAAE;QAC1C,eAAe,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAClB,KAAC,IAAI,oBAAK,IAAI,cACZ,gBAAM,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,SAAS,EAAC,WAAW,aACnE,QAAQ,EAET,cAAK,SAAS,EAAC,0BAA0B,YACvC,MAAC,MAAM,IAAC,IAAI,EAAC,QAAQ,EAAC,QAAQ,EAAE,OAAO,IAAI,oBAAoB,aAC5D,OAAO,IAAI,CACV,KAAC,IAAI,IACH,IAAI,EAAC,qBAAqB,EAC1B,SAAS,EAAC,2BAA2B,GACrC,CACH,EACA,OAAO,IAAI,uBAAuB;gCACjC,CAAC,CAAC,uBAAuB;gCACzB,CAAC,CAAC,gBAAgB,IACb,GACL,IACD,IACF,CACR,CAAC;IAEF,OAAO,CACL,KAAC,oBAAoB,IACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,YAEb,WAAW,GACS,CACxB,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { ReactNode, useTransition } from 'react';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Form } from '@ekanos/ui/form';\nimport { Icon } from '@ekanos/ui/icon';\nimport { FieldValues, UseFormReturn } from 'react-hook-form';\n\nimport { BaseActivationDialog } from './base-activation-dialog';\n\nexport interface BaseActivationFormProps<\n TFieldValues extends FieldValues = FieldValues,\n> {\n isOpen: boolean;\n onClose: () => void;\n title: string;\n description: string;\n /**\n * Generic rather than `UseFormReturn<any>`: with current react-hook-form\n * typings a concrete `UseFormReturn<T>` is NOT assignable to\n * `UseFormReturn<any>` (the `control._options.validate` parameter is\n * checked contravariantly), which broke every out-of-repo consumer — the\n * clean-room pack test caught it. Inference from the `form` prop keeps\n * every call site source-compatible.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n form: UseFormReturn<TFieldValues, any, any>;\n onSubmit: (data: TFieldValues) => Promise<void> | void;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n submitButtonText?: string;\n submitButtonLoadingText?: string;\n submitButtonDisabled?: boolean;\n /** @deprecated No longer needed — cancel button is hidden by default */\n showCancelButton?: boolean;\n /** @deprecated No longer needed — submit button is centered by default */\n centerSubmitButton?: boolean;\n maxWidth?: string;\n /** Render inline instead of in a dialog */\n inline?: boolean;\n}\n\nexport function BaseActivationForm<\n TFieldValues extends FieldValues = FieldValues,\n>({\n isOpen,\n onClose,\n title,\n description,\n form,\n onSubmit,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n submitButtonText = 'Activate Integration',\n submitButtonLoadingText,\n submitButtonDisabled = false,\n maxWidth,\n inline = false,\n}: BaseActivationFormProps<TFieldValues>) {\n const [pending, startTransition] = useTransition();\n const loading = pending || isLoading;\n\n const handleSubmit = (data: TFieldValues) => {\n startTransition(async () => {\n await onSubmit(data);\n });\n };\n\n const formContent = (\n <Form {...form}>\n <form onSubmit={form.handleSubmit(handleSubmit)} className=\"space-y-6\">\n {children}\n\n <div className=\"flex justify-center pt-4\">\n <Button type=\"submit\" disabled={loading || submitButtonDisabled}>\n {loading && (\n <Icon\n name=\"fa-light fa-spinner\"\n className=\"mr-2 h-4 w-4 animate-spin\"\n />\n )}\n {loading && submitButtonLoadingText\n ? submitButtonLoadingText\n : submitButtonText}\n </Button>\n </div>\n </form>\n </Form>\n );\n\n return (\n <BaseActivationDialog\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n description={description}\n isLoading={loading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n onSuccessClose={onSuccessClose}\n maxWidth={maxWidth}\n inline={inline}\n >\n {formContent}\n </BaseActivationDialog>\n );\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { MarketplaceTileProps } from '../types/integration.js';
|
|
2
|
+
export interface ProductCategory {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
slug: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
}
|
|
8
|
+
export type BaseMarketplaceTileProps = MarketplaceTileProps & {
|
|
9
|
+
/** The display name of the product */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Full description of the product (used for detail pages) */
|
|
12
|
+
description: string;
|
|
13
|
+
/** URL-friendly identifier for the product */
|
|
14
|
+
slug: string;
|
|
15
|
+
/** Short summary text displayed on the tile (1-2 sentences) */
|
|
16
|
+
summary?: string;
|
|
17
|
+
/** Name of the company/brand that provides this product */
|
|
18
|
+
brandName?: string;
|
|
19
|
+
/** Path to the product logo image */
|
|
20
|
+
logoPath?: string;
|
|
21
|
+
/** Text for the call-to-action button */
|
|
22
|
+
ctaText?: string;
|
|
23
|
+
/** External URL for the CTA (opens in new tab when set) */
|
|
24
|
+
ctaLink?: string;
|
|
25
|
+
/** Array of categories this product belongs to */
|
|
26
|
+
categories?: ProductCategory[];
|
|
27
|
+
/** Hex color from product schema for tile theming */
|
|
28
|
+
productColor?: string;
|
|
29
|
+
/** Additional CSS classes to apply to the tile */
|
|
30
|
+
className?: string;
|
|
31
|
+
/** Internal navigation URL for the integration detail page */
|
|
32
|
+
href?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function BaseMarketplaceTile(props: BaseMarketplaceTileProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { Badge } from '@ekanos/ui/badge';
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@ekanos/ui/card';
|
|
6
|
+
import { Icon } from '@ekanos/ui/icon';
|
|
7
|
+
import { If } from '@ekanos/ui/if';
|
|
8
|
+
import { cn } from '@ekanos/ui/utils';
|
|
9
|
+
export function BaseMarketplaceTile(props) {
|
|
10
|
+
const { name, summary, brandName, logoPath, categories = [], productColor, className, href, ctaLink, } = props;
|
|
11
|
+
const displayTitle = name || 'Unknown Product';
|
|
12
|
+
const displaySummary = summary;
|
|
13
|
+
const displayBrandName = brandName || displayTitle;
|
|
14
|
+
const cardContent = (_jsxs(Card, { className: cn('group bg-card relative flex h-full w-full cursor-pointer flex-col overflow-hidden border-0 pt-3 pb-2 shadow-sm transition-all duration-300 hover:shadow-md', className), children: [_jsxs(CardHeader, { className: "flex flex-col items-center pt-6 pb-3 text-center", children: [_jsx("div", { className: "mb-3", children: logoPath ? (_jsxs("div", { className: "flex h-16 w-16 items-center justify-center", children: [_jsx("img", { src: logoPath, alt: `${displayBrandName} logo`, width: 64, height: 64, className: "max-h-full max-w-full object-contain", onError: (e) => {
|
|
15
|
+
const target = e.target;
|
|
16
|
+
target.style.display = 'none';
|
|
17
|
+
if (target.nextSibling) {
|
|
18
|
+
target.nextSibling.style.display = 'flex';
|
|
19
|
+
}
|
|
20
|
+
} }), _jsx("div", { className: "bg-border ring-border/50 hidden h-full w-full items-center justify-center rounded-lg text-2xl font-semibold ring-1", children: displayBrandName.charAt(0).toUpperCase() })] })) : (_jsx("div", { className: "bg-border ring-border/50 flex h-16 w-16 items-center justify-center rounded-lg border-2 text-2xl font-semibold ring-1", children: displayBrandName.charAt(0).toUpperCase() })) }), _jsx(CardTitle, { className: "line-clamp-2 text-xl leading-tight font-semibold", children: displayTitle }), _jsx(If, { condition: categories.length > 0, children: _jsxs("div", { className: "mt-2 flex flex-wrap justify-center gap-1", children: [categories.slice(0, 2).map((cat) => (_jsx(Badge, { variant: "secondary", className: "px-2 py-0.5 text-xs font-medium", style: {
|
|
21
|
+
backgroundColor: productColor
|
|
22
|
+
? `${productColor}66`
|
|
23
|
+
: undefined,
|
|
24
|
+
}, children: cat.name }, cat.id))), _jsx(If, { condition: categories.length > 2, children: _jsxs(Badge, { variant: "secondary", className: "px-2 py-0.5 text-xs font-medium", style: {
|
|
25
|
+
backgroundColor: productColor
|
|
26
|
+
? `${productColor}66`
|
|
27
|
+
: undefined,
|
|
28
|
+
}, children: ["+", categories.length - 2] }) })] }) })] }), _jsx(CardContent, { className: "flex-1 pb-8 text-center", children: _jsx(If, { condition: displaySummary, children: _jsx(CardDescription, { className: "text-muted-foreground line-clamp-2 text-base leading-relaxed", children: displaySummary }) }) }), _jsx(If, { condition: displayBrandName !== displayTitle, children: _jsx("p", { className: "text-muted-foreground/70 absolute right-3 bottom-3 text-sm", children: displayBrandName }) }), _jsx(If, { condition: !!ctaLink, children: _jsx("div", { className: "text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors", "aria-hidden": "true", children: _jsx(Icon, { name: "fa-light fa-arrow-up-right-from-square", className: "h-4 w-4" }) }) })] }));
|
|
29
|
+
// External link: opens in new tab
|
|
30
|
+
if (ctaLink) {
|
|
31
|
+
return (_jsx("a", { href: ctaLink, target: "_blank", rel: "noopener noreferrer", className: "block h-full", "aria-label": `${displayTitle} (opens in new tab)`, children: cardContent }));
|
|
32
|
+
}
|
|
33
|
+
// Internal link: uses Next.js Link
|
|
34
|
+
if (href) {
|
|
35
|
+
return (_jsx(Link, { href: href, className: "block h-full", children: cardContent }));
|
|
36
|
+
}
|
|
37
|
+
// Fallback: render without Link (for backwards compatibility)
|
|
38
|
+
return cardContent;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=base-marketplace-tile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-marketplace-tile.js","sourceRoot":"","sources":["../../src/components/base-marketplace-tile.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport Link from 'next/link';\n\nimport { Badge } from '@ekanos/ui/badge';\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { MarketplaceTileProps } from '../types/integration';\n\n// Type for product category\nexport interface ProductCategory {\n id: string;\n name: string;\n slug: string;\n description?: string;\n}\n\nexport type BaseMarketplaceTileProps = MarketplaceTileProps & {\n /** The display name of the product */\n name: string;\n /** Full description of the product (used for detail pages) */\n description: string;\n /** URL-friendly identifier for the product */\n slug: string;\n /** Short summary text displayed on the tile (1-2 sentences) */\n summary?: string;\n /** Name of the company/brand that provides this product */\n brandName?: string;\n /** Path to the product logo image */\n logoPath?: string;\n /** Text for the call-to-action button */\n ctaText?: string;\n /** External URL for the CTA (opens in new tab when set) */\n ctaLink?: string;\n\n /** Array of categories this product belongs to */\n categories?: ProductCategory[];\n\n /** Hex color from product schema for tile theming */\n productColor?: string;\n /** Additional CSS classes to apply to the tile */\n className?: string;\n\n /** Internal navigation URL for the integration detail page */\n href?: string;\n};\n\nexport function BaseMarketplaceTile(props: BaseMarketplaceTileProps) {\n const {\n name,\n summary,\n brandName,\n logoPath,\n categories = [],\n productColor,\n className,\n href,\n ctaLink,\n } = props;\n\n const displayTitle = name || 'Unknown Product';\n const displaySummary = summary;\n const displayBrandName = brandName || displayTitle;\n\n const cardContent = (\n <Card\n className={cn(\n 'group bg-card relative flex h-full w-full cursor-pointer flex-col overflow-hidden border-0 pt-3 pb-2 shadow-sm transition-all duration-300 hover:shadow-md',\n className,\n )}\n >\n {/* Header: Centered Logo, Title, Categories */}\n <CardHeader className=\"flex flex-col items-center pt-6 pb-3 text-center\">\n {/* Logo */}\n <div className=\"mb-3\">\n {logoPath ? (\n <div className=\"flex h-16 w-16 items-center justify-center\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoPath}\n alt={`${displayBrandName} logo`}\n width={64}\n height={64}\n className=\"max-h-full max-w-full object-contain\"\n onError={(e) => {\n const target = e.target as HTMLImageElement;\n target.style.display = 'none';\n if (target.nextSibling) {\n (target.nextSibling as HTMLElement).style.display = 'flex';\n }\n }}\n />\n <div className=\"bg-border ring-border/50 hidden h-full w-full items-center justify-center rounded-lg text-2xl font-semibold ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n </div>\n ) : (\n <div className=\"bg-border ring-border/50 flex h-16 w-16 items-center justify-center rounded-lg border-2 text-2xl font-semibold ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n )}\n </div>\n\n {/* Title */}\n <CardTitle className=\"line-clamp-2 text-xl leading-tight font-semibold\">\n {displayTitle}\n </CardTitle>\n\n {/* Category Badges - Under Title */}\n <If condition={categories.length > 0}>\n <div className=\"mt-2 flex flex-wrap justify-center gap-1\">\n {categories.slice(0, 2).map((cat) => (\n <Badge\n key={cat.id}\n variant=\"secondary\"\n className=\"px-2 py-0.5 text-xs font-medium\"\n style={{\n backgroundColor: productColor\n ? `${productColor}66`\n : undefined,\n }}\n >\n {cat.name}\n </Badge>\n ))}\n <If condition={categories.length > 2}>\n <Badge\n variant=\"secondary\"\n className=\"px-2 py-0.5 text-xs font-medium\"\n style={{\n backgroundColor: productColor\n ? `${productColor}66`\n : undefined,\n }}\n >\n +{categories.length - 2}\n </Badge>\n </If>\n </div>\n </If>\n </CardHeader>\n\n {/* Content: Summary */}\n <CardContent className=\"flex-1 pb-8 text-center\">\n <If condition={displaySummary}>\n <CardDescription className=\"text-muted-foreground line-clamp-2 text-base leading-relaxed\">\n {displaySummary}\n </CardDescription>\n </If>\n </CardContent>\n\n {/* Brand Name - Bottom Right */}\n <If condition={displayBrandName !== displayTitle}>\n <p className=\"text-muted-foreground/70 absolute right-3 bottom-3 text-sm\">\n {displayBrandName}\n </p>\n </If>\n\n {/* External Link Indicator */}\n <If condition={!!ctaLink}>\n <div\n className=\"text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors\"\n aria-hidden=\"true\"\n >\n <Icon\n name=\"fa-light fa-arrow-up-right-from-square\"\n className=\"h-4 w-4\"\n />\n </div>\n </If>\n </Card>\n );\n\n // External link: opens in new tab\n if (ctaLink) {\n return (\n <a\n href={ctaLink}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block h-full\"\n aria-label={`${displayTitle} (opens in new tab)`}\n >\n {cardContent}\n </a>\n );\n }\n\n // Internal link: uses Next.js Link\n if (href) {\n return (\n <Link href={href} className=\"block h-full\">\n {cardContent}\n </Link>\n );\n }\n\n // Fallback: render without Link (for backwards compatibility)\n return cardContent;\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The path an OAuth connect route should send the browser back to.
|
|
3
|
+
*
|
|
4
|
+
* Includes the QUERY STRING, which every call site used to drop by reading
|
|
5
|
+
* `window.location.pathname` alone. That mattered the moment activation moved
|
|
6
|
+
* into a modal addressed by `?integration=<slug>` (plan 067): the browser has
|
|
7
|
+
* to land back on a URL that re-opens the modal, because the activation form
|
|
8
|
+
* is what reads `?oauth_success=true` on mount and auto-activates
|
|
9
|
+
* (`use-oauth-connection-status.ts`). Return to a bare path and the provider
|
|
10
|
+
* token is stored while the integration is silently never activated — no
|
|
11
|
+
* error, no toast, the row just stays "Ready to connect".
|
|
12
|
+
*
|
|
13
|
+
* `oauth_success` is stripped so a retry after a completed round-trip does
|
|
14
|
+
* not ask the next return to re-announce a stale success.
|
|
15
|
+
*
|
|
16
|
+
* Any NEW OAuth-style activation form must use this rather than reaching for
|
|
17
|
+
* `window.location.pathname` again.
|
|
18
|
+
*/
|
|
19
|
+
export declare function currentReturnPath(fallback?: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The path an OAuth connect route should send the browser back to.
|
|
3
|
+
*
|
|
4
|
+
* Includes the QUERY STRING, which every call site used to drop by reading
|
|
5
|
+
* `window.location.pathname` alone. That mattered the moment activation moved
|
|
6
|
+
* into a modal addressed by `?integration=<slug>` (plan 067): the browser has
|
|
7
|
+
* to land back on a URL that re-opens the modal, because the activation form
|
|
8
|
+
* is what reads `?oauth_success=true` on mount and auto-activates
|
|
9
|
+
* (`use-oauth-connection-status.ts`). Return to a bare path and the provider
|
|
10
|
+
* token is stored while the integration is silently never activated — no
|
|
11
|
+
* error, no toast, the row just stays "Ready to connect".
|
|
12
|
+
*
|
|
13
|
+
* `oauth_success` is stripped so a retry after a completed round-trip does
|
|
14
|
+
* not ask the next return to re-announce a stale success.
|
|
15
|
+
*
|
|
16
|
+
* Any NEW OAuth-style activation form must use this rather than reaching for
|
|
17
|
+
* `window.location.pathname` again.
|
|
18
|
+
*/
|
|
19
|
+
export function currentReturnPath(fallback = '/home') {
|
|
20
|
+
if (typeof window === 'undefined')
|
|
21
|
+
return fallback;
|
|
22
|
+
const url = new URL(window.location.href);
|
|
23
|
+
url.searchParams.delete('oauth_success');
|
|
24
|
+
return `${url.pathname}${url.search}`;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=current-return-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"current-return-path.js","sourceRoot":"","sources":["../../src/components/current-return-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAQ,GAAG,OAAO;IAClD,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,QAAQ,CAAC;IAEnD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC1C,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAEzC,OAAO,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;AACxC,CAAC","sourcesContent":["/**\n * The path an OAuth connect route should send the browser back to.\n *\n * Includes the QUERY STRING, which every call site used to drop by reading\n * `window.location.pathname` alone. That mattered the moment activation moved\n * into a modal addressed by `?integration=<slug>` (plan 067): the browser has\n * to land back on a URL that re-opens the modal, because the activation form\n * is what reads `?oauth_success=true` on mount and auto-activates\n * (`use-oauth-connection-status.ts`). Return to a bare path and the provider\n * token is stored while the integration is silently never activated — no\n * error, no toast, the row just stays \"Ready to connect\".\n *\n * `oauth_success` is stripped so a retry after a completed round-trip does\n * not ask the next return to re-announce a stale success.\n *\n * Any NEW OAuth-style activation form must use this rather than reaching for\n * `window.location.pathname` again.\n */\nexport function currentReturnPath(fallback = '/home'): string {\n if (typeof window === 'undefined') return fallback;\n\n const url = new URL(window.location.href);\n url.searchParams.delete('oauth_success');\n\n return `${url.pathname}${url.search}`;\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ekanos/sdk/components — client-side building blocks.
|
|
3
|
+
*
|
|
4
|
+
* Import from client components only. `Widget` is the compound API every
|
|
5
|
+
* dashboard widget renders through; the activation forms are the two
|
|
6
|
+
* sanctioned activation UX bases (credential-style and OAuth-style), and
|
|
7
|
+
* `BaseMarketplaceTile` is the matching base for the marketplace tile.
|
|
8
|
+
*
|
|
9
|
+
* The SDK OWNS these implementations — the pre-inversion re-exports through
|
|
10
|
+
* `@kit/integrations-core/components/*` are gone; core now re-exports FROM
|
|
11
|
+
* here for first-party code (one source of truth, two consumption paths —
|
|
12
|
+
* DEVELOPER_EXPERIENCE_PLAN.md §5.1). UI primitives come from `@ekanos/ui`
|
|
13
|
+
* (the published slice of the host design system), never `@kit/ui`.
|
|
14
|
+
*
|
|
15
|
+
* Server seam: nothing here imports a server module. `OAuthActivationForm`
|
|
16
|
+
* performs activation through the host-injected actions seam
|
|
17
|
+
* (`IntegrationActivationProvider`, @ekanos/sdk/hooks) — a server-action
|
|
18
|
+
* REFERENCE bound by the host, not server code shipped to the browser.
|
|
19
|
+
*
|
|
20
|
+
* `WidgetContext` is the single context identity both sides share: the
|
|
21
|
+
* host's `DashboardWidgetProvider` (host chrome — persistence callbacks,
|
|
22
|
+
* config hierarchy) provides it, the SDK's `Widget.*` reads it.
|
|
23
|
+
*
|
|
24
|
+
* Surface discipline: additions require an entry in
|
|
25
|
+
* docs/devex/sdk-export-map.md ("`@ekanos/sdk/components`").
|
|
26
|
+
*/
|
|
27
|
+
export { Widget } from './widgets/widget.js';
|
|
28
|
+
export { WidgetContext, type WidgetActions, type WidgetContextValue, type WidgetHealth, type WidgetMeta, type WidgetRenderState, type WidgetState, } from './widgets/widget-context.js';
|
|
29
|
+
export { AiPromptChip } from './widgets/ai-prompt-chip.js';
|
|
30
|
+
export { AskIcon } from './widgets/ask-icon.js';
|
|
31
|
+
export { WidgetAskBar } from './widgets/widget-ask-bar.js';
|
|
32
|
+
export { WidgetDataLoading } from './widgets/widget-state-components.js';
|
|
33
|
+
export { BaseActivationForm } from './base-activation-form.js';
|
|
34
|
+
export type { BaseActivationFormProps } from './base-activation-form.js';
|
|
35
|
+
export { ActivationDialog, ActivationInline, BaseActivationDialog, } from './base-activation-dialog.js';
|
|
36
|
+
export { BaseMarketplaceTile } from './base-marketplace-tile.js';
|
|
37
|
+
export type { BaseMarketplaceTileProps } from './base-marketplace-tile.js';
|
|
38
|
+
export { OAuthActivationForm } from './oauth-activation-form.js';
|
|
39
|
+
export type { OAuthActivationFormConfig } from './oauth-activation-form.js';
|
|
40
|
+
export { currentReturnPath } from './current-return-path.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ekanos/sdk/components — client-side building blocks.
|
|
3
|
+
*
|
|
4
|
+
* Import from client components only. `Widget` is the compound API every
|
|
5
|
+
* dashboard widget renders through; the activation forms are the two
|
|
6
|
+
* sanctioned activation UX bases (credential-style and OAuth-style), and
|
|
7
|
+
* `BaseMarketplaceTile` is the matching base for the marketplace tile.
|
|
8
|
+
*
|
|
9
|
+
* The SDK OWNS these implementations — the pre-inversion re-exports through
|
|
10
|
+
* `@kit/integrations-core/components/*` are gone; core now re-exports FROM
|
|
11
|
+
* here for first-party code (one source of truth, two consumption paths —
|
|
12
|
+
* DEVELOPER_EXPERIENCE_PLAN.md §5.1). UI primitives come from `@ekanos/ui`
|
|
13
|
+
* (the published slice of the host design system), never `@kit/ui`.
|
|
14
|
+
*
|
|
15
|
+
* Server seam: nothing here imports a server module. `OAuthActivationForm`
|
|
16
|
+
* performs activation through the host-injected actions seam
|
|
17
|
+
* (`IntegrationActivationProvider`, @ekanos/sdk/hooks) — a server-action
|
|
18
|
+
* REFERENCE bound by the host, not server code shipped to the browser.
|
|
19
|
+
*
|
|
20
|
+
* `WidgetContext` is the single context identity both sides share: the
|
|
21
|
+
* host's `DashboardWidgetProvider` (host chrome — persistence callbacks,
|
|
22
|
+
* config hierarchy) provides it, the SDK's `Widget.*` reads it.
|
|
23
|
+
*
|
|
24
|
+
* Surface discipline: additions require an entry in
|
|
25
|
+
* docs/devex/sdk-export-map.md ("`@ekanos/sdk/components`").
|
|
26
|
+
*/
|
|
27
|
+
export { Widget } from './widgets/widget.js';
|
|
28
|
+
export { WidgetContext, } from './widgets/widget-context.js';
|
|
29
|
+
export { AiPromptChip } from './widgets/ai-prompt-chip.js';
|
|
30
|
+
export { AskIcon } from './widgets/ask-icon.js';
|
|
31
|
+
export { WidgetAskBar } from './widgets/widget-ask-bar.js';
|
|
32
|
+
export { WidgetDataLoading } from './widgets/widget-state-components.js';
|
|
33
|
+
export { BaseActivationForm } from './base-activation-form.js';
|
|
34
|
+
export { ActivationDialog, ActivationInline, BaseActivationDialog, } from './base-activation-dialog.js';
|
|
35
|
+
export { BaseMarketplaceTile } from './base-marketplace-tile.js';
|
|
36
|
+
export { OAuthActivationForm } from './oauth-activation-form.js';
|
|
37
|
+
export { currentReturnPath } from './current-return-path.js';
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EACL,aAAa,GAOd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAEtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["/**\n * @ekanos/sdk/components — client-side building blocks.\n *\n * Import from client components only. `Widget` is the compound API every\n * dashboard widget renders through; the activation forms are the two\n * sanctioned activation UX bases (credential-style and OAuth-style), and\n * `BaseMarketplaceTile` is the matching base for the marketplace tile.\n *\n * The SDK OWNS these implementations — the pre-inversion re-exports through\n * `@kit/integrations-core/components/*` are gone; core now re-exports FROM\n * here for first-party code (one source of truth, two consumption paths —\n * DEVELOPER_EXPERIENCE_PLAN.md §5.1). UI primitives come from `@ekanos/ui`\n * (the published slice of the host design system), never `@kit/ui`.\n *\n * Server seam: nothing here imports a server module. `OAuthActivationForm`\n * performs activation through the host-injected actions seam\n * (`IntegrationActivationProvider`, @ekanos/sdk/hooks) — a server-action\n * REFERENCE bound by the host, not server code shipped to the browser.\n *\n * `WidgetContext` is the single context identity both sides share: the\n * host's `DashboardWidgetProvider` (host chrome — persistence callbacks,\n * config hierarchy) provides it, the SDK's `Widget.*` reads it.\n *\n * Surface discipline: additions require an entry in\n * docs/devex/sdk-export-map.md (\"`@ekanos/sdk/components`\").\n */\n\nexport { Widget } from './widgets/widget';\nexport {\n WidgetContext,\n type WidgetActions,\n type WidgetContextValue,\n type WidgetHealth,\n type WidgetMeta,\n type WidgetRenderState,\n type WidgetState,\n} from './widgets/widget-context';\nexport { AiPromptChip } from './widgets/ai-prompt-chip';\nexport { AskIcon } from './widgets/ask-icon';\nexport { WidgetAskBar } from './widgets/widget-ask-bar';\nexport { WidgetDataLoading } from './widgets/widget-state-components';\n\nexport { BaseActivationForm } from './base-activation-form';\nexport type { BaseActivationFormProps } from './base-activation-form';\nexport {\n ActivationDialog,\n ActivationInline,\n BaseActivationDialog,\n} from './base-activation-dialog';\n\nexport { BaseMarketplaceTile } from './base-marketplace-tile';\nexport type { BaseMarketplaceTileProps } from './base-marketplace-tile';\n\nexport { OAuthActivationForm } from './oauth-activation-form';\nexport type { OAuthActivationFormConfig } from './oauth-activation-form';\n\nexport { currentReturnPath } from './current-return-path';\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ConnectionCheckAction, type OAuthErrorParam } from '../hooks/use-oauth-connection-status.js';
|
|
3
|
+
import type { ActivationFormProps } from '../types/integration.js';
|
|
4
|
+
export interface OAuthActivationFormConfig {
|
|
5
|
+
/** Integration slug — used for activation, the connect route, and ids. */
|
|
6
|
+
slug: string;
|
|
7
|
+
/** Human name used in default button/banner copy, e.g. 'QuickBooks'. */
|
|
8
|
+
displayName: string;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
successMessage: string;
|
|
12
|
+
/** Defaults to "You're all set!" */
|
|
13
|
+
successTitle?: string;
|
|
14
|
+
/** Defaults to `/api/integrations/<slug>/connect`. */
|
|
15
|
+
connectPath?: string;
|
|
16
|
+
/** Defaults to `<displayName> account connected`. */
|
|
17
|
+
connectedBanner?: string;
|
|
18
|
+
/** Defaults to `Connect with <displayName>`. */
|
|
19
|
+
connectButtonText?: string;
|
|
20
|
+
/** Defaults to `Activate <displayName>`. */
|
|
21
|
+
activateButtonText?: string;
|
|
22
|
+
/** externalId is `<prefix>_<timestamp>`; prefix defaults to the slug. */
|
|
23
|
+
externalIdPrefix?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Whether returning from OAuth should immediately activate (default `true`).
|
|
26
|
+
*
|
|
27
|
+
* Set `false` for integrations that expose post-connect configuration — the
|
|
28
|
+
* user needs a chance to review or change it before activating, which
|
|
29
|
+
* auto-activation skips right past. Connecting then leaves the form on an
|
|
30
|
+
* "Activate <X>" button as a deliberate second step.
|
|
31
|
+
*/
|
|
32
|
+
autoActivateOnConnect?: boolean;
|
|
33
|
+
}
|
|
34
|
+
type OAuthActivationFormProps = ActivationFormProps & {
|
|
35
|
+
config: OAuthActivationFormConfig;
|
|
36
|
+
checkConnection: ConnectionCheckAction;
|
|
37
|
+
errorParam?: OAuthErrorParam;
|
|
38
|
+
/** Extra content (feature showcases, permission badges) rendered above the connected banner. */
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Shared activation form for OAuth-backed integrations: shows a "Connect
|
|
43
|
+
* with X" button that redirects to the integration's connect route, then
|
|
44
|
+
* auto-activates once the OAuth redirect returns with a live connection.
|
|
45
|
+
*
|
|
46
|
+
* Integrations with real activation fields (API keys, domains, …) should
|
|
47
|
+
* keep a bespoke form built on BaseActivationForm instead.
|
|
48
|
+
*/
|
|
49
|
+
export declare function OAuthActivationForm(props: OAuthActivationFormProps): import("react").JSX.Element;
|
|
50
|
+
export {};
|