@ekanos/sdk 0.1.4 → 0.1.6

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 CHANGED
@@ -501,6 +501,21 @@ export function CurrentWeatherWidget({ accountId }: IntegrationComponentProps) {
501
501
  }
502
502
  ```
503
503
 
504
+ **`Widget.Active` is intentionally flush.** Its `CardContent` renders `p-0`
505
+ with no padding, so a chart, table, or other full-bleed content can run
506
+ edge-to-edge — existing widgets depend on this. `Widget.Header` stays inset
507
+ (`px-5`), so ordinary content (text, rows, a KPI readout) rendered directly
508
+ inside `Widget.Active` sits flush against the card edges while the header
509
+ above it does not. For that case, wrap your content in `Widget.Body` — the
510
+ standard padded body (`px-5 pt-4 pb-5`, matching the header's inset) — rather
511
+ than hand-rolling a `px-*` wrapper:
512
+
513
+ ```tsx
514
+ <Widget.Active>
515
+ <Widget.Body>{/* rows, text, a KPI readout */}</Widget.Body>
516
+ </Widget.Active>
517
+ ```
518
+
504
519
  Declared like this:
505
520
 
506
521
  ```ts
@@ -1177,8 +1192,13 @@ and the host adapts the definition internally.
1177
1192
  `@ekanos/ui` components emit Tailwind class strings against Fusion's semantic
1178
1193
  tokens, and icons are Font Awesome glyphs the host loads. Import
1179
1194
  `@ekanos/ui/styles.css` (or the narrower `tokens.css` / `theme.css` /
1180
- `base.css`) to get the token layer. Outside a host that loads Font Awesome,
1181
- icons render as nothing at all — that is expected, not a bug in your code.
1195
+ `base.css`) to get the token layer — that also draws a `□` placeholder for
1196
+ icons if you load no Font Awesome at all, so "I forgot the icon font" doesn't
1197
+ look identical to "my code is broken". If the host DOES load Font Awesome but
1198
+ not the specific glyph you asked for (Free-only host, Pro-only name), the icon
1199
+ renders a circle-question disc instead, and `@ekanos/ui/icon` warns once per
1200
+ name in development. See `@ekanos/ui`'s README ("Icons: you must supply Font
1201
+ Awesome") for the Free-vs-Pro gap and how to size and re-family icons.
1182
1202
 
1183
1203
  ## Naming and validation rules, in one table
1184
1204
 
package/api-report.md CHANGED
@@ -9,7 +9,7 @@ This is the complete surface of every entrypoint. The authoring guide
9
9
  for these symbols is README.md. Entrypoints marked WORKSPACE-ONLY are
10
10
  not reachable from a published install — see the note in each section.
11
11
 
12
- Total exported names: **150** across 7 entrypoints.
12
+ Total exported names: **155** across 7 entrypoints.
13
13
 
14
14
  ## `@ekanos/sdk` (32 exports)
15
15
 
@@ -77,7 +77,7 @@ Total exported names: **150** across 7 entrypoints.
77
77
  | `WidgetState` | interface |
78
78
  | `currentReturnPath` | function |
79
79
 
80
- ## `@ekanos/sdk/hooks` (28 exports)
80
+ ## `@ekanos/sdk/hooks` (33 exports)
81
81
 
82
82
  | Export | Kind |
83
83
  | --- | --- |
@@ -93,14 +93,19 @@ Total exported names: **150** across 7 entrypoints.
93
93
  | `IntegrationActivationActions` | interface |
94
94
  | `IntegrationActivationProvider` | function |
95
95
  | `OAuthErrorParam` | interface |
96
+ | `PartnerOAuthPathOptions` | interface |
96
97
  | `PublishEventInput` | interface |
97
98
  | `UseOAuthConnectionStatusOptions` | interface |
98
99
  | `WidgetEvent` | interface |
99
100
  | `WidgetEventFilter` | interface |
100
101
  | `WidgetEventProvider` | function |
101
102
  | `WidgetEventSubscriber` | type alias |
103
+ | `createPartnerConnectionCheck` | function |
102
104
  | `fetchIntegrationStorage` | function |
103
105
  | `integrationStorageUrl` | function |
106
+ | `partnerOAuthConnectPath` | function |
107
+ | `partnerOAuthErrorParam` | function |
108
+ | `partnerOAuthStatusUrl` | function |
104
109
  | `useActivateIntegration` | function |
105
110
  | `useAskAssistant` | function |
106
111
  | `useDeactivateIntegration` | function |
@@ -1 +1 @@
1
- {"version":3,"file":"widget-preview-provider.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-preview-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,EACL,aAAa,GAId,MAAM,kBAAkB,CAAC;AAqB1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,GAAG,QAAQ,EAChB,aAAa,GAAG,KAAK,EACrB,UAAU,GAAG,KAAK,EAClB,gBAAgB,GAAG,KAAK,EACxB,aAAa,GAAG,KAAK,EACrB,gBAAgB,EAChB,UAAU,EACV,eAAe,GAAG,KAAK,EACvB,MAAM,EACN,QAAQ,GACmB;IAC3B,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC7D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAG,WAAW,CACjC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC3C,EAAE,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAE/E,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE;YACL,KAAK;YACL,SAAS;YACT,MAAM;YACN,eAAe,EAAE,KAAK;YACtB,UAAU,EAAE,KAAK;SAClB;QACD,OAAO,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;QAC1C,IAAI,EAAE;YACJ,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW;YACX,SAAS;YACT,gBAAgB;YAChB,aAAa;YACb,UAAU;YACV,UAAU;YACV,eAAe;YACf,MAAM;SACP;KACF,CAAC,EACF;QACE,eAAe;QACf,UAAU;QACV,SAAS;QACT,SAAS;QACT,WAAW;QACX,MAAM;QACN,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,MAAM;QACN,KAAK;QACL,QAAQ;QACR,KAAK;QACL,eAAe;QACf,YAAY;QACZ,QAAQ;KACT,CACF,CAAC;IAEF,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiB,CAAC;AACjE,CAAC","sourcesContent":["'use client';\n\nimport { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport {\n WidgetContext,\n type WidgetContextValue,\n type WidgetHealth,\n type WidgetRenderState,\n} from './widget-context';\n\nexport interface WidgetPreviewProviderProps {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n className?: string;\n /** Defaults to `'active'` — the state you almost always want to look at. */\n state?: WidgetRenderState;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n defaultCollapsed?: boolean;\n defaultPinned?: boolean;\n maxContentHeight?: string;\n askContext?: WidgetAskContext;\n aiFooterEnabled?: boolean;\n health?: WidgetHealth;\n children: ReactNode;\n}\n\n/**\n * Renders a widget outside the Fusion dashboard.\n *\n * `Widget.*` reads everything — render state, collapse/pin, title — from\n * `WidgetContext`, which in production is filled by the host's\n * `DashboardWidgetProvider` (config hierarchy, persistence callbacks, health\n * resolution). None of that exists in a partner's own app, a Storybook story,\n * or a component test, and without SOME provider every `Widget.*` throws. This\n * is that provider: same context value, local state only, nothing persisted.\n *\n * ```tsx\n * <WidgetPreviewProvider widgetId=\"my-widget\" title=\"My widget\">\n * <MyWidget accountId={accountId} productSlug=\"my-integration\" />\n * </WidgetPreviewProvider>\n * ```\n *\n * Flip `state` to walk every branch your widget renders (`'loading'`,\n * `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.\n */\nexport function WidgetPreviewProvider({\n widgetId,\n title,\n subtitle,\n description,\n className,\n state = 'active',\n isCollapsible = false,\n isPinnable = false,\n defaultCollapsed = false,\n defaultPinned = false,\n maxContentHeight,\n askContext,\n aiFooterEnabled = false,\n health,\n children,\n}: WidgetPreviewProviderProps) {\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const [pinned, setPinned] = useState(defaultPinned);\n\n const toggleCollapsed = useCallback(\n () => setCollapsed((previous) => !previous),\n [],\n );\n\n const togglePinned = useCallback(() => setPinned((previous) => !previous), []);\n\n const value = useMemo<WidgetContextValue>(\n () => ({\n state: {\n state,\n collapsed,\n pinned,\n pendingCollapse: false,\n pendingPin: false,\n },\n actions: { toggleCollapsed, togglePinned },\n meta: {\n widgetId,\n title,\n subtitle,\n description,\n className,\n maxContentHeight,\n isCollapsible,\n isPinnable,\n askContext,\n aiFooterEnabled,\n health,\n },\n }),\n [\n aiFooterEnabled,\n askContext,\n className,\n collapsed,\n description,\n health,\n isCollapsible,\n isPinnable,\n maxContentHeight,\n pinned,\n state,\n subtitle,\n title,\n toggleCollapsed,\n togglePinned,\n widgetId,\n ],\n );\n\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n"]}
1
+ {"version":3,"file":"widget-preview-provider.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-preview-provider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAkB,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,EACL,aAAa,GAId,MAAM,kBAAkB,CAAC;AAqB1B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EACpC,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,GAAG,QAAQ,EAChB,aAAa,GAAG,KAAK,EACrB,UAAU,GAAG,KAAK,EAClB,gBAAgB,GAAG,KAAK,EACxB,aAAa,GAAG,KAAK,EACrB,gBAAgB,EAChB,UAAU,EACV,eAAe,GAAG,KAAK,EACvB,MAAM,EACN,QAAQ,GACmB;IAC3B,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAC7D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IAEpD,MAAM,eAAe,GAAG,WAAW,CACjC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAC3C,EAAE,CACH,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAC9B,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EACxC,EAAE,CACH,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK,EAAE;YACL,KAAK;YACL,SAAS;YACT,MAAM;YACN,eAAe,EAAE,KAAK;YACtB,UAAU,EAAE,KAAK;SAClB;QACD,OAAO,EAAE,EAAE,eAAe,EAAE,YAAY,EAAE;QAC1C,IAAI,EAAE;YACJ,QAAQ;YACR,KAAK;YACL,QAAQ;YACR,WAAW;YACX,SAAS;YACT,gBAAgB;YAChB,aAAa;YACb,UAAU;YACV,UAAU;YACV,eAAe;YACf,MAAM;SACP;KACF,CAAC,EACF;QACE,eAAe;QACf,UAAU;QACV,SAAS;QACT,SAAS;QACT,WAAW;QACX,MAAM;QACN,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,MAAM;QACN,KAAK;QACL,QAAQ;QACR,KAAK;QACL,eAAe;QACf,YAAY;QACZ,QAAQ;KACT,CACF,CAAC;IAEF,OAAO,KAAC,aAAa,IAAC,KAAK,EAAE,KAAK,YAAG,QAAQ,GAAiB,CAAC;AACjE,CAAC","sourcesContent":["'use client';\n\nimport { type ReactNode, useCallback, useMemo, useState } from 'react';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport {\n WidgetContext,\n type WidgetContextValue,\n type WidgetHealth,\n type WidgetRenderState,\n} from './widget-context';\n\nexport interface WidgetPreviewProviderProps {\n widgetId: string;\n title?: string;\n subtitle?: string;\n description?: string;\n className?: string;\n /** Defaults to `'active'` — the state you almost always want to look at. */\n state?: WidgetRenderState;\n isCollapsible?: boolean;\n isPinnable?: boolean;\n defaultCollapsed?: boolean;\n defaultPinned?: boolean;\n maxContentHeight?: string;\n askContext?: WidgetAskContext;\n aiFooterEnabled?: boolean;\n health?: WidgetHealth;\n children: ReactNode;\n}\n\n/**\n * Renders a widget outside the Fusion dashboard.\n *\n * `Widget.*` reads everything — render state, collapse/pin, title — from\n * `WidgetContext`, which in production is filled by the host's\n * `DashboardWidgetProvider` (config hierarchy, persistence callbacks, health\n * resolution). None of that exists in a partner's own app, a Storybook story,\n * or a component test, and without SOME provider every `Widget.*` throws. This\n * is that provider: same context value, local state only, nothing persisted.\n *\n * ```tsx\n * <WidgetPreviewProvider widgetId=\"my-widget\" title=\"My widget\">\n * <MyWidget accountId={accountId} productSlug=\"my-integration\" />\n * </WidgetPreviewProvider>\n * ```\n *\n * Flip `state` to walk every branch your widget renders (`'loading'`,\n * `'error'`, `'inactive'`, `'disabled'`) without faking a network condition.\n */\nexport function WidgetPreviewProvider({\n widgetId,\n title,\n subtitle,\n description,\n className,\n state = 'active',\n isCollapsible = false,\n isPinnable = false,\n defaultCollapsed = false,\n defaultPinned = false,\n maxContentHeight,\n askContext,\n aiFooterEnabled = false,\n health,\n children,\n}: WidgetPreviewProviderProps) {\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const [pinned, setPinned] = useState(defaultPinned);\n\n const toggleCollapsed = useCallback(\n () => setCollapsed((previous) => !previous),\n [],\n );\n\n const togglePinned = useCallback(\n () => setPinned((previous) => !previous),\n [],\n );\n\n const value = useMemo<WidgetContextValue>(\n () => ({\n state: {\n state,\n collapsed,\n pinned,\n pendingCollapse: false,\n pendingPin: false,\n },\n actions: { toggleCollapsed, togglePinned },\n meta: {\n widgetId,\n title,\n subtitle,\n description,\n className,\n maxContentHeight,\n isCollapsible,\n isPinnable,\n askContext,\n aiFooterEnabled,\n health,\n },\n }),\n [\n aiFooterEnabled,\n askContext,\n className,\n collapsed,\n description,\n health,\n isCollapsible,\n isPinnable,\n maxContentHeight,\n pinned,\n state,\n subtitle,\n title,\n toggleCollapsed,\n togglePinned,\n widgetId,\n ],\n );\n\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n"]}
@@ -12,6 +12,40 @@ declare function WidgetHeader({ title, subtitle, description, children, }: {
12
12
  description?: string;
13
13
  children?: ReactNode;
14
14
  }): import("react").JSX.Element | null;
15
+ /**
16
+ * The standard padded body for content rendered inside `Widget.Active`.
17
+ *
18
+ * `Widget.Active`'s own `CardContent` renders `p-0` — deliberately, so a
19
+ * full-bleed chart or table can run edge-to-edge inside the card. That means
20
+ * ordinary content (text, rows, a KPI readout) is left flush against the
21
+ * card's sides while `Widget.Header` stays inset (`px-5`) unless the body
22
+ * supplies its own padding. `Widget.Body` is that padding: `px-5` matches
23
+ * the header's horizontal inset so header and body align, `pt-4 pb-5`
24
+ * balances the header's `pb-4`/card's bottom edge.
25
+ *
26
+ * Use it for any widget whose content is NOT already full-bleed:
27
+ *
28
+ * ```tsx
29
+ * <Widget.Active>
30
+ * <Widget.Body>{/* rows, text, a KPI readout *\/}</Widget.Body>
31
+ * </Widget.Active>
32
+ * ```
33
+ *
34
+ * Skip it — render children directly inside `Widget.Active` — when the
35
+ * content itself needs to run flush to the card edges (a chart, a table with
36
+ * its own header row, an image).
37
+ */
38
+ declare function WidgetBody({ className, children, }: {
39
+ className?: string;
40
+ children: ReactNode;
41
+ }): import("react").JSX.Element;
42
+ /**
43
+ * Renders `children` once the widget's state is `active`. Intentionally
44
+ * flush: its `CardContent` renders `p-0` (no horizontal/vertical inset) so a
45
+ * chart, table, or other full-bleed content can run edge-to-edge — existing
46
+ * widgets depend on this. For ordinary padded content, wrap `children` in
47
+ * `Widget.Body` rather than hand-rolling a `px-*` wrapper.
48
+ */
15
49
  declare function WidgetActive({ children }: {
16
50
  children: ReactNode;
17
51
  }): import("react").JSX.Element | null;
@@ -79,6 +113,7 @@ export declare const Widget: {
79
113
  Header: typeof WidgetHeader;
80
114
  DataState: typeof WidgetDataState;
81
115
  Active: typeof WidgetActive;
116
+ Body: typeof WidgetBody;
82
117
  Loading: typeof WidgetLoading;
83
118
  Error: typeof WidgetError;
84
119
  Inactive: typeof WidgetInactive;
@@ -77,6 +77,39 @@ function StaticBody({ maxContentHeight, children, }) {
77
77
  maxHeight: maxContentHeight !== null && maxContentHeight !== void 0 ? maxContentHeight : undefined,
78
78
  }, children: _jsx("div", { ref: innerRef, children: children }) }) }));
79
79
  }
80
+ /**
81
+ * The standard padded body for content rendered inside `Widget.Active`.
82
+ *
83
+ * `Widget.Active`'s own `CardContent` renders `p-0` — deliberately, so a
84
+ * full-bleed chart or table can run edge-to-edge inside the card. That means
85
+ * ordinary content (text, rows, a KPI readout) is left flush against the
86
+ * card's sides while `Widget.Header` stays inset (`px-5`) unless the body
87
+ * supplies its own padding. `Widget.Body` is that padding: `px-5` matches
88
+ * the header's horizontal inset so header and body align, `pt-4 pb-5`
89
+ * balances the header's `pb-4`/card's bottom edge.
90
+ *
91
+ * Use it for any widget whose content is NOT already full-bleed:
92
+ *
93
+ * ```tsx
94
+ * <Widget.Active>
95
+ * <Widget.Body>{/* rows, text, a KPI readout *\/}</Widget.Body>
96
+ * </Widget.Active>
97
+ * ```
98
+ *
99
+ * Skip it — render children directly inside `Widget.Active` — when the
100
+ * content itself needs to run flush to the card edges (a chart, a table with
101
+ * its own header row, an image).
102
+ */
103
+ function WidgetBody({ className, children, }) {
104
+ return (_jsx("div", { className: cn('flex flex-1 flex-col gap-4 px-5 pt-4 pb-5', className), children: children }));
105
+ }
106
+ /**
107
+ * Renders `children` once the widget's state is `active`. Intentionally
108
+ * flush: its `CardContent` renders `p-0` (no horizontal/vertical inset) so a
109
+ * chart, table, or other full-bleed content can run edge-to-edge — existing
110
+ * widgets depend on this. For ordinary padded content, wrap `children` in
111
+ * `Widget.Body` rather than hand-rolling a `px-*` wrapper.
112
+ */
80
113
  function WidgetActive({ children }) {
81
114
  const { state, meta } = useWidget();
82
115
  if (state.state !== 'active')
@@ -196,6 +229,7 @@ export const Widget = {
196
229
  Header: WidgetHeader,
197
230
  DataState: WidgetDataState,
198
231
  Active: WidgetActive,
232
+ Body: WidgetBody,
199
233
  Loading: WidgetLoading,
200
234
  Error: WidgetError,
201
235
  Inactive: WidgetInactive,
@@ -1 +1 @@
1
- {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../../src/components/widgets/widget.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { Component, type ReactNode, use, useMemo } from 'react';\n\nimport { Button } from '@ekanos/ui/button';\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@ekanos/ui/tooltip';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { useAnimatedHeight } from './use-animated-height';\nimport { WidgetAskBar } from './widget-ask-bar';\nimport { WidgetContext, type WidgetRenderState } from './widget-context';\nimport {\n WidgetDisabledState,\n WidgetErrorState,\n WidgetInactiveState,\n WidgetLoadingState,\n} from './widget-state-components';\n\n// ─── Error Boundary ─────────────────────────────────────────────────────────\n\nclass WidgetErrorBoundary extends Component<\n { widgetId: string; children: ReactNode },\n { hasError: boolean }\n> {\n state = { hasError: false };\n static getDerivedStateFromError() {\n return { hasError: true };\n }\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n console.error(`Widget \"${this.props.widgetId}\" crashed:`, error, info);\n }\n render() {\n if (this.state.hasError) {\n return (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-3 p-8\">\n <div className=\"bg-destructive/10 flex h-16 w-16 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"text-destructive h-8 w-8\"\n />\n </div>\n <p className=\"text-muted-foreground text-base font-medium\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedTitle\"\n defaults=\"Something went wrong\"\n />\n </p>\n <p className=\"text-muted-foreground max-w-xs text-center text-sm\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedBody\"\n defaults=\"This widget encountered an error. Try refreshing the page.\"\n />\n </p>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\n// ─── Hook ───────────────────────────────────────────────────────────────────\n\nfunction useWidget() {\n const ctx = use(WidgetContext);\n if (!ctx) {\n throw new Error(\n 'Widget.* compound components must be rendered inside a widget provider. ' +\n 'On the Fusion dashboard the host supplies one. Anywhere else — your own ' +\n 'app, a story, a component test — wrap the widget in ' +\n \"`WidgetPreviewProvider` from '@ekanos/sdk/components'.\",\n );\n }\n return ctx;\n}\n\n// ─── Card ───────────────────────────────────────────────────────────────────\n\nfunction WidgetCard({ children }: { children: ReactNode }) {\n const { meta } = useWidget();\n return (\n <Card\n className={cn(\n // Neutralize the shadcn card's own py-6/gap-6 box model — widget\n // chrome owns all vertical spacing via its header/content/footer.\n 'shadow-widget relative flex h-full flex-col gap-0 overflow-hidden rounded-lg border-0 py-0',\n meta.className,\n )}\n data-widget-id={meta.widgetId}\n >\n {children}\n </Card>\n );\n}\n\n// ─── Header ─────────────────────────────────────────────────────────────────\n\nfunction CollapseButton() {\n const { state, actions, meta } = useWidget();\n return (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={actions.toggleCollapsed}\n disabled={state.pendingCollapse}\n className=\"bg-background dark:bg-secondary relative h-6 w-6 rounded-full before:absolute before:top-1/2 before:left-1/2 before:h-11 before:w-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\"\n aria-expanded={!state.collapsed}\n aria-controls={`widget-content-${meta.widgetId}`}\n aria-label={\n state.collapsed\n ? `Expand ${meta.title ?? 'widget'}`\n : `Collapse ${meta.title ?? 'widget'}`\n }\n >\n <Icon\n name=\"fa-light fa-chevron-right\"\n className={cn(\n 'h-4 w-4 transition-transform duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n !state.collapsed && 'rotate-90',\n )}\n />\n </Button>\n );\n}\n\nfunction WidgetHeader({\n title,\n subtitle,\n description,\n children,\n}: {\n /** Override the provider's title — useful when the rendered name differs from the config name. */\n title?: string;\n /** Override the provider's subtitle — useful for widget-computed values. */\n subtitle?: string;\n /** Override the provider's description — useful for widget-computed values. */\n description?: string;\n children?: ReactNode;\n}) {\n const { meta } = useWidget();\n const effectiveTitle = title ?? meta.title;\n const effectiveSubtitle = subtitle ?? meta.subtitle;\n const effectiveDescription = description ?? meta.description;\n if (!effectiveTitle) return null;\n return (\n <CardHeader className=\"flex-shrink-0 border-b pt-6 pb-4 [.border-b]:pb-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n {meta.isCollapsible ? <CollapseButton /> : null}\n {effectiveDescription ? (\n <>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger\n render={\n <CardTitle className=\"cursor-help text-lg font-semibold\" />\n }\n >\n {effectiveTitle}\n </TooltipTrigger>\n <TooltipContent>\n <p>{effectiveDescription}</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n <span className=\"sr-only\">{effectiveDescription}</span>\n </>\n ) : (\n <CardTitle className=\"text-lg font-semibold\">\n {effectiveTitle}\n </CardTitle>\n )}\n {effectiveSubtitle && (\n <span className=\"text-muted-foreground text-base font-medium\">\n {effectiveSubtitle}\n </span>\n )}\n </div>\n {children && (\n <div className=\"flex shrink-0 items-center gap-2\">{children}</div>\n )}\n </div>\n </CardHeader>\n );\n}\n\n// ─── State-gated content ────────────────────────────────────────────────────\n\nfunction CollapsibleBody({\n collapsed,\n maxContentHeight,\n widgetId,\n children,\n}: {\n collapsed: boolean;\n maxContentHeight?: string;\n widgetId: string;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn(\n 'grid min-h-0 flex-1 transition-[grid-template-rows] duration-300 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n collapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]',\n )}\n >\n <div className=\"min-h-0 overflow-hidden\">\n <CardContent\n id={`widget-content-${widgetId}`}\n className={cn(\n 'flex h-full min-h-0 flex-1 flex-col overflow-y-auto p-0',\n 'transition-opacity duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n collapsed ? 'opacity-0' : 'opacity-100',\n )}\n style={maxContentHeight ? { maxHeight: maxContentHeight } : undefined}\n >\n {children}\n </CardContent>\n </div>\n </div>\n );\n}\n\nfunction StaticBody({\n maxContentHeight,\n children,\n}: {\n maxContentHeight?: string;\n children: ReactNode;\n}) {\n const { innerRef, height } = useAnimatedHeight();\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n <div\n className=\"overflow-hidden transition-[height] duration-250 ease-out motion-reduce:transition-none\"\n style={{\n height: height === 'auto' ? ('auto' as const) : height,\n maxHeight: maxContentHeight ?? undefined,\n }}\n >\n <div ref={innerRef}>{children}</div>\n </div>\n </CardContent>\n );\n}\n\nfunction WidgetActive({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n const body = (\n <WidgetErrorBoundary widgetId={meta.widgetId}>\n {children}\n </WidgetErrorBoundary>\n );\n return meta.isCollapsible ? (\n <CollapsibleBody\n collapsed={state.collapsed}\n maxContentHeight={meta.maxContentHeight}\n widgetId={meta.widgetId}\n >\n {body}\n </CollapsibleBody>\n ) : (\n <StaticBody maxContentHeight={meta.maxContentHeight}>{body}</StaticBody>\n );\n}\n\nfunction WidgetLoading({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'loading') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetLoadingState />}\n </CardContent>\n );\n}\n\nfunction WidgetError({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'error') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetErrorState />}\n </CardContent>\n );\n}\n\nfunction WidgetInactive({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'inactive') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetInactiveState />}\n </CardContent>\n );\n}\n\nfunction WidgetDisabled({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'disabled') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetDisabledState />}\n </CardContent>\n );\n}\n\n// ─── Footers ────────────────────────────────────────────────────────────────\n\nfunction WidgetFooter({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n return (\n <CardFooter\n className={cn(\n 'flex-shrink-0 pb-6 transition-opacity duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n meta.isCollapsible && state.collapsed\n ? 'pointer-events-none opacity-0'\n : 'opacity-100',\n )}\n aria-hidden={meta.isCollapsible && state.collapsed}\n >\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetActions({ children }: { children: ReactNode }) {\n return (\n <CardFooter className=\"bg-muted flex-shrink-0 justify-end border-t py-3\">\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetAskFooter({ context }: { context?: WidgetAskContext }) {\n const { state, meta } = useWidget();\n const effectiveContext = context ?? meta.askContext;\n const shouldShow =\n (meta.aiFooterEnabled || !!effectiveContext) &&\n state.state === 'active' &&\n !state.collapsed;\n if (!shouldShow) return null;\n return (\n <WidgetAskBar\n context={\n effectiveContext ?? {\n widgetId: meta.widgetId,\n title: meta.title ?? meta.widgetId,\n snapshot: null,\n suggestedPrompt: `Tell me about ${meta.title ?? 'this widget'}.`,\n }\n }\n />\n );\n}\n\n/**\n * Renders a subtle \"data may be stale\" banner when the widget's integration is\n * `degraded` (but still active). Unhealthy integrations route to `disabled`\n * instead, so this only fires for the degraded middle ground.\n */\nfunction WidgetHealthFooter() {\n const { state, meta } = useWidget();\n const shouldShow =\n state.state === 'active' &&\n !state.collapsed &&\n meta.health?.status === 'degraded';\n if (!shouldShow) return null;\n return (\n <div className=\"text-warning border-warning/30 bg-warning/5 flex items-center gap-2 border-t px-4 py-2 text-xs\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"h-4 w-4 shrink-0\"\n aria-hidden\n />\n <span>\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNotice\"\n defaults=\"This integration is degraded — data may be stale.\"\n />\n </span>\n </div>\n );\n}\n\n// ─── Data state override ────────────────────────────────────────────────────\n\n/**\n * Wraps children in a sub-provider that overrides the effective render state\n * with the widget's own data loading / error state. Only overrides when the\n * parent state is `active` — so the integration's `inactive` / `disabled` /\n * `loading` (initial) states still win.\n *\n * Read the parent integration state BEFORE wrapping in this component if you\n * need it to gate data fetching:\n *\n * ```tsx\n * const { state } = use(WidgetContext);\n * const integrationActive = state.state === 'active';\n * const { isLoading, isError } = useMyData(accountId, integrationActive);\n * return (\n * <Widget.DataState loading={isLoading} error={isError}>\n * <Widget.Card>…</Widget.Card>\n * </Widget.DataState>\n * );\n * ```\n */\nfunction WidgetDataState({\n loading,\n error,\n inactive,\n children,\n}: {\n loading?: boolean;\n error?: boolean;\n /**\n * Force the `inactive` render state even though the integration itself is\n * active. Use for widget-level \"not connected\" states (e.g. the product is\n * activated but the upstream OAuth connection is missing). Takes priority\n * over `loading` / `error`.\n */\n inactive?: boolean;\n children: ReactNode;\n}) {\n const parent = useWidget();\n const overrideState: WidgetRenderState =\n parent.state.state !== 'active'\n ? parent.state.state\n : inactive\n ? 'inactive'\n : loading\n ? 'loading'\n : error\n ? 'error'\n : 'active';\n const value = useMemo(\n () => ({\n state: { ...parent.state, state: overrideState },\n actions: parent.actions,\n meta: parent.meta,\n }),\n [parent.state, parent.actions, parent.meta, overrideState],\n );\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n\n// ─── Compound export ────────────────────────────────────────────────────────\n\nexport const Widget = {\n Card: WidgetCard,\n Header: WidgetHeader,\n DataState: WidgetDataState,\n Active: WidgetActive,\n Loading: WidgetLoading,\n Error: WidgetError,\n Inactive: WidgetInactive,\n Disabled: WidgetDisabled,\n Footer: WidgetFooter,\n Actions: WidgetActions,\n AskFooter: WidgetAskFooter,\n HealthFooter: WidgetHealthFooter,\n};\n"]}
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../../src/components/widgets/widget.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { Component, type ReactNode, use, useMemo } from 'react';\n\nimport { Button } from '@ekanos/ui/button';\nimport {\n Card,\n CardContent,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport {\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@ekanos/ui/tooltip';\nimport { Trans } from '@ekanos/ui/trans';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { useAnimatedHeight } from './use-animated-height';\nimport { WidgetAskBar } from './widget-ask-bar';\nimport { WidgetContext, type WidgetRenderState } from './widget-context';\nimport {\n WidgetDisabledState,\n WidgetErrorState,\n WidgetInactiveState,\n WidgetLoadingState,\n} from './widget-state-components';\n\n// ─── Error Boundary ─────────────────────────────────────────────────────────\n\nclass WidgetErrorBoundary extends Component<\n { widgetId: string; children: ReactNode },\n { hasError: boolean }\n> {\n state = { hasError: false };\n static getDerivedStateFromError() {\n return { hasError: true };\n }\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n console.error(`Widget \"${this.props.widgetId}\" crashed:`, error, info);\n }\n render() {\n if (this.state.hasError) {\n return (\n <div className=\"flex flex-1 flex-col items-center justify-center gap-3 p-8\">\n <div className=\"bg-destructive/10 flex h-16 w-16 items-center justify-center rounded-full\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"text-destructive h-8 w-8\"\n />\n </div>\n <p className=\"text-muted-foreground text-base font-medium\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedTitle\"\n defaults=\"Something went wrong\"\n />\n </p>\n <p className=\"text-muted-foreground max-w-xs text-center text-sm\">\n <Trans\n i18nKey=\"dashboard:widgetCrashedBody\"\n defaults=\"This widget encountered an error. Try refreshing the page.\"\n />\n </p>\n </div>\n );\n }\n return this.props.children;\n }\n}\n\n// ─── Hook ───────────────────────────────────────────────────────────────────\n\nfunction useWidget() {\n const ctx = use(WidgetContext);\n if (!ctx) {\n throw new Error(\n 'Widget.* compound components must be rendered inside a widget provider. ' +\n 'On the Fusion dashboard the host supplies one. Anywhere else — your own ' +\n 'app, a story, a component test — wrap the widget in ' +\n \"`WidgetPreviewProvider` from '@ekanos/sdk/components'.\",\n );\n }\n return ctx;\n}\n\n// ─── Card ───────────────────────────────────────────────────────────────────\n\nfunction WidgetCard({ children }: { children: ReactNode }) {\n const { meta } = useWidget();\n return (\n <Card\n className={cn(\n // Neutralize the shadcn card's own py-6/gap-6 box model — widget\n // chrome owns all vertical spacing via its header/content/footer.\n 'shadow-widget relative flex h-full flex-col gap-0 overflow-hidden rounded-lg border-0 py-0',\n meta.className,\n )}\n data-widget-id={meta.widgetId}\n >\n {children}\n </Card>\n );\n}\n\n// ─── Header ─────────────────────────────────────────────────────────────────\n\nfunction CollapseButton() {\n const { state, actions, meta } = useWidget();\n return (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={actions.toggleCollapsed}\n disabled={state.pendingCollapse}\n className=\"bg-background dark:bg-secondary relative h-6 w-6 rounded-full before:absolute before:top-1/2 before:left-1/2 before:h-11 before:w-11 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\"\n aria-expanded={!state.collapsed}\n aria-controls={`widget-content-${meta.widgetId}`}\n aria-label={\n state.collapsed\n ? `Expand ${meta.title ?? 'widget'}`\n : `Collapse ${meta.title ?? 'widget'}`\n }\n >\n <Icon\n name=\"fa-light fa-chevron-right\"\n className={cn(\n 'h-4 w-4 transition-transform duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n !state.collapsed && 'rotate-90',\n )}\n />\n </Button>\n );\n}\n\nfunction WidgetHeader({\n title,\n subtitle,\n description,\n children,\n}: {\n /** Override the provider's title — useful when the rendered name differs from the config name. */\n title?: string;\n /** Override the provider's subtitle — useful for widget-computed values. */\n subtitle?: string;\n /** Override the provider's description — useful for widget-computed values. */\n description?: string;\n children?: ReactNode;\n}) {\n const { meta } = useWidget();\n const effectiveTitle = title ?? meta.title;\n const effectiveSubtitle = subtitle ?? meta.subtitle;\n const effectiveDescription = description ?? meta.description;\n if (!effectiveTitle) return null;\n return (\n <CardHeader className=\"flex-shrink-0 border-b pt-6 pb-4 [.border-b]:pb-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2\">\n {meta.isCollapsible ? <CollapseButton /> : null}\n {effectiveDescription ? (\n <>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger\n render={\n <CardTitle className=\"cursor-help text-lg font-semibold\" />\n }\n >\n {effectiveTitle}\n </TooltipTrigger>\n <TooltipContent>\n <p>{effectiveDescription}</p>\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n <span className=\"sr-only\">{effectiveDescription}</span>\n </>\n ) : (\n <CardTitle className=\"text-lg font-semibold\">\n {effectiveTitle}\n </CardTitle>\n )}\n {effectiveSubtitle && (\n <span className=\"text-muted-foreground text-base font-medium\">\n {effectiveSubtitle}\n </span>\n )}\n </div>\n {children && (\n <div className=\"flex shrink-0 items-center gap-2\">{children}</div>\n )}\n </div>\n </CardHeader>\n );\n}\n\n// ─── State-gated content ────────────────────────────────────────────────────\n\nfunction CollapsibleBody({\n collapsed,\n maxContentHeight,\n widgetId,\n children,\n}: {\n collapsed: boolean;\n maxContentHeight?: string;\n widgetId: string;\n children: ReactNode;\n}) {\n return (\n <div\n className={cn(\n 'grid min-h-0 flex-1 transition-[grid-template-rows] duration-300 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n collapsed ? 'grid-rows-[0fr]' : 'grid-rows-[1fr]',\n )}\n >\n <div className=\"min-h-0 overflow-hidden\">\n <CardContent\n id={`widget-content-${widgetId}`}\n className={cn(\n 'flex h-full min-h-0 flex-1 flex-col overflow-y-auto p-0',\n 'transition-opacity duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n collapsed ? 'opacity-0' : 'opacity-100',\n )}\n style={maxContentHeight ? { maxHeight: maxContentHeight } : undefined}\n >\n {children}\n </CardContent>\n </div>\n </div>\n );\n}\n\nfunction StaticBody({\n maxContentHeight,\n children,\n}: {\n maxContentHeight?: string;\n children: ReactNode;\n}) {\n const { innerRef, height } = useAnimatedHeight();\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n <div\n className=\"overflow-hidden transition-[height] duration-250 ease-out motion-reduce:transition-none\"\n style={{\n height: height === 'auto' ? ('auto' as const) : height,\n maxHeight: maxContentHeight ?? undefined,\n }}\n >\n <div ref={innerRef}>{children}</div>\n </div>\n </CardContent>\n );\n}\n\n/**\n * The standard padded body for content rendered inside `Widget.Active`.\n *\n * `Widget.Active`'s own `CardContent` renders `p-0` — deliberately, so a\n * full-bleed chart or table can run edge-to-edge inside the card. That means\n * ordinary content (text, rows, a KPI readout) is left flush against the\n * card's sides while `Widget.Header` stays inset (`px-5`) unless the body\n * supplies its own padding. `Widget.Body` is that padding: `px-5` matches\n * the header's horizontal inset so header and body align, `pt-4 pb-5`\n * balances the header's `pb-4`/card's bottom edge.\n *\n * Use it for any widget whose content is NOT already full-bleed:\n *\n * ```tsx\n * <Widget.Active>\n * <Widget.Body>{/* rows, text, a KPI readout *\\/}</Widget.Body>\n * </Widget.Active>\n * ```\n *\n * Skip it — render children directly inside `Widget.Active` — when the\n * content itself needs to run flush to the card edges (a chart, a table with\n * its own header row, an image).\n */\nfunction WidgetBody({\n className,\n children,\n}: {\n className?: string;\n children: ReactNode;\n}) {\n return (\n <div className={cn('flex flex-1 flex-col gap-4 px-5 pt-4 pb-5', className)}>\n {children}\n </div>\n );\n}\n\n/**\n * Renders `children` once the widget's state is `active`. Intentionally\n * flush: its `CardContent` renders `p-0` (no horizontal/vertical inset) so a\n * chart, table, or other full-bleed content can run edge-to-edge — existing\n * widgets depend on this. For ordinary padded content, wrap `children` in\n * `Widget.Body` rather than hand-rolling a `px-*` wrapper.\n */\nfunction WidgetActive({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n const body = (\n <WidgetErrorBoundary widgetId={meta.widgetId}>\n {children}\n </WidgetErrorBoundary>\n );\n return meta.isCollapsible ? (\n <CollapsibleBody\n collapsed={state.collapsed}\n maxContentHeight={meta.maxContentHeight}\n widgetId={meta.widgetId}\n >\n {body}\n </CollapsibleBody>\n ) : (\n <StaticBody maxContentHeight={meta.maxContentHeight}>{body}</StaticBody>\n );\n}\n\nfunction WidgetLoading({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'loading') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetLoadingState />}\n </CardContent>\n );\n}\n\nfunction WidgetError({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'error') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetErrorState />}\n </CardContent>\n );\n}\n\nfunction WidgetInactive({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'inactive') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetInactiveState />}\n </CardContent>\n );\n}\n\nfunction WidgetDisabled({ children }: { children?: ReactNode }) {\n const { state } = useWidget();\n if (state.state !== 'disabled') return null;\n return (\n <CardContent className=\"flex min-h-0 flex-1 flex-col p-0\">\n {children ?? <WidgetDisabledState />}\n </CardContent>\n );\n}\n\n// ─── Footers ────────────────────────────────────────────────────────────────\n\nfunction WidgetFooter({ children }: { children: ReactNode }) {\n const { state, meta } = useWidget();\n if (state.state !== 'active') return null;\n return (\n <CardFooter\n className={cn(\n 'flex-shrink-0 pb-6 transition-opacity duration-200 ease-[cubic-bezier(0.25,1,0.5,1)] motion-reduce:transition-none',\n meta.isCollapsible && state.collapsed\n ? 'pointer-events-none opacity-0'\n : 'opacity-100',\n )}\n aria-hidden={meta.isCollapsible && state.collapsed}\n >\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetActions({ children }: { children: ReactNode }) {\n return (\n <CardFooter className=\"bg-muted flex-shrink-0 justify-end border-t py-3\">\n {children}\n </CardFooter>\n );\n}\n\nfunction WidgetAskFooter({ context }: { context?: WidgetAskContext }) {\n const { state, meta } = useWidget();\n const effectiveContext = context ?? meta.askContext;\n const shouldShow =\n (meta.aiFooterEnabled || !!effectiveContext) &&\n state.state === 'active' &&\n !state.collapsed;\n if (!shouldShow) return null;\n return (\n <WidgetAskBar\n context={\n effectiveContext ?? {\n widgetId: meta.widgetId,\n title: meta.title ?? meta.widgetId,\n snapshot: null,\n suggestedPrompt: `Tell me about ${meta.title ?? 'this widget'}.`,\n }\n }\n />\n );\n}\n\n/**\n * Renders a subtle \"data may be stale\" banner when the widget's integration is\n * `degraded` (but still active). Unhealthy integrations route to `disabled`\n * instead, so this only fires for the degraded middle ground.\n */\nfunction WidgetHealthFooter() {\n const { state, meta } = useWidget();\n const shouldShow =\n state.state === 'active' &&\n !state.collapsed &&\n meta.health?.status === 'degraded';\n if (!shouldShow) return null;\n return (\n <div className=\"text-warning border-warning/30 bg-warning/5 flex items-center gap-2 border-t px-4 py-2 text-xs\">\n <Icon\n name=\"fa-light fa-circle-exclamation\"\n className=\"h-4 w-4 shrink-0\"\n aria-hidden\n />\n <span>\n <Trans\n i18nKey=\"common:integrationHealth.degradedWidgetNotice\"\n defaults=\"This integration is degraded — data may be stale.\"\n />\n </span>\n </div>\n );\n}\n\n// ─── Data state override ────────────────────────────────────────────────────\n\n/**\n * Wraps children in a sub-provider that overrides the effective render state\n * with the widget's own data loading / error state. Only overrides when the\n * parent state is `active` — so the integration's `inactive` / `disabled` /\n * `loading` (initial) states still win.\n *\n * Read the parent integration state BEFORE wrapping in this component if you\n * need it to gate data fetching:\n *\n * ```tsx\n * const { state } = use(WidgetContext);\n * const integrationActive = state.state === 'active';\n * const { isLoading, isError } = useMyData(accountId, integrationActive);\n * return (\n * <Widget.DataState loading={isLoading} error={isError}>\n * <Widget.Card>…</Widget.Card>\n * </Widget.DataState>\n * );\n * ```\n */\nfunction WidgetDataState({\n loading,\n error,\n inactive,\n children,\n}: {\n loading?: boolean;\n error?: boolean;\n /**\n * Force the `inactive` render state even though the integration itself is\n * active. Use for widget-level \"not connected\" states (e.g. the product is\n * activated but the upstream OAuth connection is missing). Takes priority\n * over `loading` / `error`.\n */\n inactive?: boolean;\n children: ReactNode;\n}) {\n const parent = useWidget();\n const overrideState: WidgetRenderState =\n parent.state.state !== 'active'\n ? parent.state.state\n : inactive\n ? 'inactive'\n : loading\n ? 'loading'\n : error\n ? 'error'\n : 'active';\n const value = useMemo(\n () => ({\n state: { ...parent.state, state: overrideState },\n actions: parent.actions,\n meta: parent.meta,\n }),\n [parent.state, parent.actions, parent.meta, overrideState],\n );\n return <WidgetContext value={value}>{children}</WidgetContext>;\n}\n\n// ─── Compound export ────────────────────────────────────────────────────────\n\nexport const Widget = {\n Card: WidgetCard,\n Header: WidgetHeader,\n DataState: WidgetDataState,\n Active: WidgetActive,\n Body: WidgetBody,\n Loading: WidgetLoading,\n Error: WidgetError,\n Inactive: WidgetInactive,\n Disabled: WidgetDisabled,\n Footer: WidgetFooter,\n Actions: WidgetActions,\n AskFooter: WidgetAskFooter,\n HealthFooter: WidgetHealthFooter,\n};\n"]}
@@ -22,6 +22,8 @@ export { fetchIntegrationStorage, integrationStorageUrl, } from './fetch-integra
22
22
  export type { FetchIntegrationStorageOptions } from './fetch-integration-storage.js';
23
23
  export { useOAuthConnectionStatus, useOAuthWidgetGate, } from './use-oauth-connection-status.js';
24
24
  export type { ConnectionCheckAction, ConnectionCheckResult, OAuthErrorParam, UseOAuthConnectionStatusOptions, } from './use-oauth-connection-status.js';
25
+ export { createPartnerConnectionCheck, partnerOAuthConnectPath, partnerOAuthErrorParam, partnerOAuthStatusUrl, } from './partner-oauth.js';
26
+ export type { PartnerOAuthPathOptions } from './partner-oauth.js';
25
27
  export { WidgetEventProvider, useWidgetEventBus } from './widget-event-context.js';
26
28
  export { useWidgetEvents } from './use-widget-events.js';
27
29
  export type { PublishEventInput, WidgetEvent, WidgetEventFilter, WidgetEventSubscriber, } from './widget-event-types.js';
@@ -24,6 +24,11 @@ export { IntegrationActivationProvider } from './activation-actions-context.js';
24
24
  export { fetchIntegrationStorage, integrationStorageUrl, } from './fetch-integration-storage.js';
25
25
  // OAuth connection status
26
26
  export { useOAuthConnectionStatus, useOAuthWidgetGate, } from './use-oauth-connection-status.js';
27
+ // Hosted partner OAuth transport (host routes:
28
+ // GET /api/integrations/[slug]/oauth/{connect,callback,status}). A partner
29
+ // passes `connectPath: partnerOAuthConnectPath(slug)` to OAuthActivationForm
30
+ // and `createPartnerConnectionCheck(slug)` as its `checkConnection`.
31
+ export { createPartnerConnectionCheck, partnerOAuthConnectPath, partnerOAuthErrorParam, partnerOAuthStatusUrl, } from './partner-oauth.js';
27
32
  // Widget Event Bus
28
33
  export { WidgetEventProvider, useWidgetEventBus } from './widget-event-context.js';
29
34
  export { useWidgetEvents } from './use-widget-events.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,4CAA4C;AAC5C,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAU7E,kDAAkD;AAClD,mEAAmE;AACnE,qEAAqE;AACrE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AAGrC,0BAA0B;AAC1B,OAAO,EACL,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAQvC,mBAAmB;AACnB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAQtD,wBAAwB;AACxB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,uBAAuB,GACxB,MAAM,yBAAyB,CAAC","sourcesContent":["/**\n * @ekanos/sdk/hooks — client-side hooks.\n *\n * Import from client components only. The SDK OWNS these implementations;\n * `@kit/integrations-core/hooks` re-exports them for first-party code, so\n * there is one source of truth with two consumption paths\n * so there is one source of truth with two consumption paths.\n *\n * Server seam: nothing here imports a server module. The activation hooks\n * reach the host's `'use server'` actions ONLY through the\n * `IntegrationActivationProvider` injection seam\n * (activation-actions-context.tsx) — that is what keeps this entrypoint free\n * of `server-only` in a partner's module graph.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\n// Activation (host-injected server actions)\nexport {\n useActivateIntegration,\n useDeactivateIntegration,\n} from './use-activate-integration';\nexport { IntegrationActivationProvider } from './activation-actions-context';\nexport type {\n ActivateIntegrationAction,\n ActivateIntegrationInput,\n DeactivateIntegrationAction,\n DeactivateIntegrationInput,\n IntegrationActionResult,\n IntegrationActivationActions,\n} from './activation-actions-context';\n\n// Generic client-side storage bridge (host route:\n// GET /api/integrations/[slug]/storage). Serves only keys declared\n// `clientReadable: true`; a key that did not opt in reads as absent.\nexport {\n fetchIntegrationStorage,\n integrationStorageUrl,\n} from './fetch-integration-storage';\nexport type { FetchIntegrationStorageOptions } from './fetch-integration-storage';\n\n// OAuth connection status\nexport {\n useOAuthConnectionStatus,\n useOAuthWidgetGate,\n} from './use-oauth-connection-status';\nexport type {\n ConnectionCheckAction,\n ConnectionCheckResult,\n OAuthErrorParam,\n UseOAuthConnectionStatusOptions,\n} from './use-oauth-connection-status';\n\n// Widget Event Bus\nexport { WidgetEventProvider, useWidgetEventBus } from './widget-event-context';\nexport { useWidgetEvents } from './use-widget-events';\nexport type {\n PublishEventInput,\n WidgetEvent,\n WidgetEventFilter,\n WidgetEventSubscriber,\n} from './widget-event-types';\n\n// Widget Ask-Bar Bridge\nexport {\n AskAssistantProvider,\n useAskAssistant,\n useRegisterAskAssistant,\n} from './ask-assistant-context';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,4CAA4C;AAC5C,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,8BAA8B,CAAC;AAU7E,kDAAkD;AAClD,mEAAmE;AACnE,qEAAqE;AACrE,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,6BAA6B,CAAC;AAGrC,0BAA0B;AAC1B,OAAO,EACL,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAQvC,+CAA+C;AAC/C,2EAA2E;AAC3E,6EAA6E;AAC7E,qEAAqE;AACrE,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AAGzB,mBAAmB;AACnB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAQtD,wBAAwB;AACxB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,uBAAuB,GACxB,MAAM,yBAAyB,CAAC","sourcesContent":["/**\n * @ekanos/sdk/hooks — client-side hooks.\n *\n * Import from client components only. The SDK OWNS these implementations;\n * `@kit/integrations-core/hooks` re-exports them for first-party code, so\n * there is one source of truth with two consumption paths\n * so there is one source of truth with two consumption paths.\n *\n * Server seam: nothing here imports a server module. The activation hooks\n * reach the host's `'use server'` actions ONLY through the\n * `IntegrationActivationProvider` injection seam\n * (activation-actions-context.tsx) — that is what keeps this entrypoint free\n * of `server-only` in a partner's module graph.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\n// Activation (host-injected server actions)\nexport {\n useActivateIntegration,\n useDeactivateIntegration,\n} from './use-activate-integration';\nexport { IntegrationActivationProvider } from './activation-actions-context';\nexport type {\n ActivateIntegrationAction,\n ActivateIntegrationInput,\n DeactivateIntegrationAction,\n DeactivateIntegrationInput,\n IntegrationActionResult,\n IntegrationActivationActions,\n} from './activation-actions-context';\n\n// Generic client-side storage bridge (host route:\n// GET /api/integrations/[slug]/storage). Serves only keys declared\n// `clientReadable: true`; a key that did not opt in reads as absent.\nexport {\n fetchIntegrationStorage,\n integrationStorageUrl,\n} from './fetch-integration-storage';\nexport type { FetchIntegrationStorageOptions } from './fetch-integration-storage';\n\n// OAuth connection status\nexport {\n useOAuthConnectionStatus,\n useOAuthWidgetGate,\n} from './use-oauth-connection-status';\nexport type {\n ConnectionCheckAction,\n ConnectionCheckResult,\n OAuthErrorParam,\n UseOAuthConnectionStatusOptions,\n} from './use-oauth-connection-status';\n\n// Hosted partner OAuth transport (host routes:\n// GET /api/integrations/[slug]/oauth/{connect,callback,status}). A partner\n// passes `connectPath: partnerOAuthConnectPath(slug)` to OAuthActivationForm\n// and `createPartnerConnectionCheck(slug)` as its `checkConnection`.\nexport {\n createPartnerConnectionCheck,\n partnerOAuthConnectPath,\n partnerOAuthErrorParam,\n partnerOAuthStatusUrl,\n} from './partner-oauth';\nexport type { PartnerOAuthPathOptions } from './partner-oauth';\n\n// Widget Event Bus\nexport { WidgetEventProvider, useWidgetEventBus } from './widget-event-context';\nexport { useWidgetEvents } from './use-widget-events';\nexport type {\n PublishEventInput,\n WidgetEvent,\n WidgetEventFilter,\n WidgetEventSubscriber,\n} from './widget-event-types';\n\n// Widget Ask-Bar Bridge\nexport {\n AskAssistantProvider,\n useAskAssistant,\n useRegisterAskAssistant,\n} from './ask-assistant-context';\n"]}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The browser half of the hosted partner OAuth transport.
3
+ *
4
+ * A partner's `oauth` declaration names the provider endpoints and the
5
+ * client-credential secrets; the HOST owns the flow — authorize redirect,
6
+ * `state`, callback, code exchange, and the `onTokens` call — behind three
7
+ * generic routes it serves for every partner integration:
8
+ *
9
+ * GET /api/integrations/[slug]/oauth/connect?accountId=…&returnPath=…
10
+ * GET /api/integrations/[slug]/oauth/callback (the provider redirects here)
11
+ * GET /api/integrations/[slug]/oauth/status?accountId=…
12
+ *
13
+ * These helpers are the only place a partner package should reference those
14
+ * paths, so a route move is one SDK change rather than a hunt through every
15
+ * integration. `OAuthActivationForm`'s `connectPath` default still points at
16
+ * the per-integration `/connect` route first-party packages ship themselves;
17
+ * a partner passes `connectPath: partnerOAuthConnectPath(slug)` explicitly.
18
+ *
19
+ * ```tsx
20
+ * <OAuthActivationForm
21
+ * {...props}
22
+ * config={{ slug, displayName: 'Acme', connectPath: partnerOAuthConnectPath(slug), … }}
23
+ * checkConnection={createPartnerConnectionCheck(slug)}
24
+ * errorParam={partnerOAuthErrorParam(slug)}
25
+ * />
26
+ * ```
27
+ */
28
+ import type { ConnectionCheckAction, OAuthErrorParam } from './use-oauth-connection-status.js';
29
+ export interface PartnerOAuthPathOptions {
30
+ /**
31
+ * Overrides the base path. Exists for harnesses and tests that serve the
32
+ * routes from somewhere other than the host's own origin; production code
33
+ * should leave it alone.
34
+ */
35
+ baseUrl?: string;
36
+ }
37
+ /** The hosted connect route the activation form sends the browser to. */
38
+ export declare function partnerOAuthConnectPath(integrationSlug: string, options?: PartnerOAuthPathOptions): string;
39
+ /** The hosted status route `createPartnerConnectionCheck` reads. */
40
+ export declare function partnerOAuthStatusUrl(integrationSlug: string, options: PartnerOAuthPathOptions & {
41
+ accountId: string;
42
+ }): string;
43
+ /**
44
+ * The error query param the hosted callback appends on failure:
45
+ * `<slug>_oauth_error=<code>`. Codes are the transport's, so the messages
46
+ * live here rather than in every partner package.
47
+ */
48
+ export declare function partnerOAuthErrorParam(integrationSlug: string): OAuthErrorParam;
49
+ /**
50
+ * A `ConnectionCheckAction` backed by the hosted status route. The route
51
+ * answers 200 with the standard envelope for both outcomes — `{ success:
52
+ * true, data: { obtainedAt } }` or `{ success: false, error }` — and a non-OK
53
+ * status only when the request itself is unauthorized, so a query error
54
+ * surfaces as an error rather than as a misleading "not connected".
55
+ */
56
+ export declare function createPartnerConnectionCheck(integrationSlug: string, options?: PartnerOAuthPathOptions): ConnectionCheckAction;
@@ -0,0 +1,63 @@
1
+ const DEFAULT_BASE_URL = '/api/integrations';
2
+ /** The hosted connect route the activation form sends the browser to. */
3
+ export function partnerOAuthConnectPath(integrationSlug, options = {}) {
4
+ var _a;
5
+ const base = (_a = options.baseUrl) !== null && _a !== void 0 ? _a : DEFAULT_BASE_URL;
6
+ return `${base}/${integrationSlug}/oauth/connect`;
7
+ }
8
+ /** The hosted status route `createPartnerConnectionCheck` reads. */
9
+ export function partnerOAuthStatusUrl(integrationSlug, options) {
10
+ var _a;
11
+ const base = (_a = options.baseUrl) !== null && _a !== void 0 ? _a : DEFAULT_BASE_URL;
12
+ const query = new URLSearchParams({ accountId: options.accountId });
13
+ return `${base}/${integrationSlug}/oauth/status?${query}`;
14
+ }
15
+ /**
16
+ * The error query param the hosted callback appends on failure:
17
+ * `<slug>_oauth_error=<code>`. Codes are the transport's, so the messages
18
+ * live here rather than in every partner package.
19
+ */
20
+ export function partnerOAuthErrorParam(integrationSlug) {
21
+ return {
22
+ name: `${integrationSlug}_oauth_error`,
23
+ toMessage: (value) => { var _a; return (_a = PARTNER_OAUTH_ERROR_MESSAGES[value]) !== null && _a !== void 0 ? _a : null; },
24
+ };
25
+ }
26
+ const PARTNER_OAUTH_ERROR_MESSAGES = {
27
+ denied: 'Access was denied at the provider. Try connecting again.',
28
+ expired: 'The connection attempt expired. Try connecting again.',
29
+ invalid: 'The connection attempt could not be verified. Try connecting again.',
30
+ unauthorized: 'You need to be signed in as the person who started the connection.',
31
+ not_configured: 'This integration is not configured for your workspace yet. Ask an administrator to add its OAuth credentials.',
32
+ exchange_failed: 'The provider rejected the connection. Try again, or contact support if it keeps failing.',
33
+ save_failed: 'Connected, but the credentials could not be saved. Try again.',
34
+ };
35
+ /**
36
+ * A `ConnectionCheckAction` backed by the hosted status route. The route
37
+ * answers 200 with the standard envelope for both outcomes — `{ success:
38
+ * true, data: { obtainedAt } }` or `{ success: false, error }` — and a non-OK
39
+ * status only when the request itself is unauthorized, so a query error
40
+ * surfaces as an error rather than as a misleading "not connected".
41
+ */
42
+ export function createPartnerConnectionCheck(integrationSlug, options = {}) {
43
+ return async ({ accountId }) => {
44
+ const url = partnerOAuthStatusUrl(integrationSlug, Object.assign(Object.assign({}, options), { accountId }));
45
+ const response = await globalThis.fetch(url, {
46
+ headers: { accept: 'application/json' },
47
+ });
48
+ if (!response.ok) {
49
+ throw new Error(`Could not read the '${integrationSlug}' connection status (${response.status} ${response.statusText}).`);
50
+ }
51
+ const payload = await response.json();
52
+ if (!isConnectionCheckResult(payload)) {
53
+ throw new Error(`The '${integrationSlug}' status route returned an unexpected payload.`);
54
+ }
55
+ return payload;
56
+ };
57
+ }
58
+ function isConnectionCheckResult(value) {
59
+ return (typeof value === 'object' &&
60
+ value !== null &&
61
+ typeof value.success === 'boolean');
62
+ }
63
+ //# sourceMappingURL=partner-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"partner-oauth.js","sourceRoot":"","sources":["../../src/hooks/partner-oauth.ts"],"names":[],"mappings":"AAiCA,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAW7C,yEAAyE;AACzE,MAAM,UAAU,uBAAuB,CACrC,eAAuB,EACvB,UAAmC,EAAE;;IAErC,MAAM,IAAI,GAAG,MAAA,OAAO,CAAC,OAAO,mCAAI,gBAAgB,CAAC;IACjD,OAAO,GAAG,IAAI,IAAI,eAAe,gBAAgB,CAAC;AACpD,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,qBAAqB,CACnC,eAAuB,EACvB,OAAwD;;IAExD,MAAM,IAAI,GAAG,MAAA,OAAO,CAAC,OAAO,mCAAI,gBAAgB,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACpE,OAAO,GAAG,IAAI,IAAI,eAAe,iBAAiB,KAAK,EAAE,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,eAAuB;IAEvB,OAAO;QACL,IAAI,EAAE,GAAG,eAAe,cAAc;QACtC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,WAAC,OAAA,MAAA,4BAA4B,CAAC,KAAK,CAAC,mCAAI,IAAI,CAAA,EAAA;KAClE,CAAC;AACJ,CAAC;AAED,MAAM,4BAA4B,GAA2B;IAC3D,MAAM,EAAE,0DAA0D;IAClE,OAAO,EAAE,uDAAuD;IAChE,OAAO,EACL,qEAAqE;IACvE,YAAY,EACV,oEAAoE;IACtE,cAAc,EACZ,+GAA+G;IACjH,eAAe,EACb,0FAA0F;IAC5F,WAAW,EAAE,+DAA+D;CAC7E,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAC1C,eAAuB,EACvB,UAAmC,EAAE;IAErC,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;QAC7B,MAAM,GAAG,GAAG,qBAAqB,CAAC,eAAe,kCAC5C,OAAO,KACV,SAAS,IACT,CAAC;QAEH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE;YAC3C,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,uBAAuB,eAAe,wBAAwB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,IAAI,CACzG,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAE/C,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CACb,QAAQ,eAAe,gDAAgD,CACxE,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAC9B,KAAc;IAEd,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,OAAQ,KAA+B,CAAC,OAAO,KAAK,SAAS,CAC9D,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The browser half of the hosted partner OAuth transport.\n *\n * A partner's `oauth` declaration names the provider endpoints and the\n * client-credential secrets; the HOST owns the flow — authorize redirect,\n * `state`, callback, code exchange, and the `onTokens` call — behind three\n * generic routes it serves for every partner integration:\n *\n * GET /api/integrations/[slug]/oauth/connect?accountId=…&returnPath=…\n * GET /api/integrations/[slug]/oauth/callback (the provider redirects here)\n * GET /api/integrations/[slug]/oauth/status?accountId=…\n *\n * These helpers are the only place a partner package should reference those\n * paths, so a route move is one SDK change rather than a hunt through every\n * integration. `OAuthActivationForm`'s `connectPath` default still points at\n * the per-integration `/connect` route first-party packages ship themselves;\n * a partner passes `connectPath: partnerOAuthConnectPath(slug)` explicitly.\n *\n * ```tsx\n * <OAuthActivationForm\n * {...props}\n * config={{ slug, displayName: 'Acme', connectPath: partnerOAuthConnectPath(slug), … }}\n * checkConnection={createPartnerConnectionCheck(slug)}\n * errorParam={partnerOAuthErrorParam(slug)}\n * />\n * ```\n */\nimport type {\n ConnectionCheckAction,\n ConnectionCheckResult,\n OAuthErrorParam,\n} from './use-oauth-connection-status';\n\nconst DEFAULT_BASE_URL = '/api/integrations';\n\nexport interface PartnerOAuthPathOptions {\n /**\n * Overrides the base path. Exists for harnesses and tests that serve the\n * routes from somewhere other than the host's own origin; production code\n * should leave it alone.\n */\n baseUrl?: string;\n}\n\n/** The hosted connect route the activation form sends the browser to. */\nexport function partnerOAuthConnectPath(\n integrationSlug: string,\n options: PartnerOAuthPathOptions = {},\n): string {\n const base = options.baseUrl ?? DEFAULT_BASE_URL;\n return `${base}/${integrationSlug}/oauth/connect`;\n}\n\n/** The hosted status route `createPartnerConnectionCheck` reads. */\nexport function partnerOAuthStatusUrl(\n integrationSlug: string,\n options: PartnerOAuthPathOptions & { accountId: string },\n): string {\n const base = options.baseUrl ?? DEFAULT_BASE_URL;\n const query = new URLSearchParams({ accountId: options.accountId });\n return `${base}/${integrationSlug}/oauth/status?${query}`;\n}\n\n/**\n * The error query param the hosted callback appends on failure:\n * `<slug>_oauth_error=<code>`. Codes are the transport's, so the messages\n * live here rather than in every partner package.\n */\nexport function partnerOAuthErrorParam(\n integrationSlug: string,\n): OAuthErrorParam {\n return {\n name: `${integrationSlug}_oauth_error`,\n toMessage: (value) => PARTNER_OAUTH_ERROR_MESSAGES[value] ?? null,\n };\n}\n\nconst PARTNER_OAUTH_ERROR_MESSAGES: Record<string, string> = {\n denied: 'Access was denied at the provider. Try connecting again.',\n expired: 'The connection attempt expired. Try connecting again.',\n invalid:\n 'The connection attempt could not be verified. Try connecting again.',\n unauthorized:\n 'You need to be signed in as the person who started the connection.',\n not_configured:\n 'This integration is not configured for your workspace yet. Ask an administrator to add its OAuth credentials.',\n exchange_failed:\n 'The provider rejected the connection. Try again, or contact support if it keeps failing.',\n save_failed: 'Connected, but the credentials could not be saved. Try again.',\n};\n\n/**\n * A `ConnectionCheckAction` backed by the hosted status route. The route\n * answers 200 with the standard envelope for both outcomes — `{ success:\n * true, data: { obtainedAt } }` or `{ success: false, error }` — and a non-OK\n * status only when the request itself is unauthorized, so a query error\n * surfaces as an error rather than as a misleading \"not connected\".\n */\nexport function createPartnerConnectionCheck(\n integrationSlug: string,\n options: PartnerOAuthPathOptions = {},\n): ConnectionCheckAction {\n return async ({ accountId }) => {\n const url = partnerOAuthStatusUrl(integrationSlug, {\n ...options,\n accountId,\n });\n\n const response = await globalThis.fetch(url, {\n headers: { accept: 'application/json' },\n });\n\n if (!response.ok) {\n throw new Error(\n `Could not read the '${integrationSlug}' connection status (${response.status} ${response.statusText}).`,\n );\n }\n\n const payload: unknown = await response.json();\n\n if (!isConnectionCheckResult(payload)) {\n throw new Error(\n `The '${integrationSlug}' status route returned an unexpected payload.`,\n );\n }\n\n return payload;\n };\n}\n\nfunction isConnectionCheckResult(\n value: unknown,\n): value is ConnectionCheckResult {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { success?: unknown }).success === 'boolean'\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@ekanos/sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
+ "private": false,
4
5
  "type": "module",
5
6
  "description": "The official SDK for building Ekanos integrations.",
6
7
  "license": "MIT",
@@ -49,8 +50,8 @@
49
50
  "access": "public"
50
51
  },
51
52
  "dependencies": {
52
- "@ekanos/integration-schema": "0.1.4",
53
- "@ekanos/ui": "0.1.4",
53
+ "@ekanos/integration-schema": "0.1.6",
54
+ "@ekanos/ui": "0.1.6",
54
55
  "@supabase/supabase-js": "2.87.1",
55
56
  "server-only": "^0.0.1"
56
57
  },