@ekanos/sdk 0.2.0 → 0.4.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.
Files changed (68) hide show
  1. package/README.md +113 -33
  2. package/api-report.md +15 -4
  3. package/dist/components/base-activation-dialog.d.ts +15 -5
  4. package/dist/components/base-activation-dialog.js +31 -5
  5. package/dist/components/base-activation-dialog.js.map +1 -1
  6. package/dist/components/base-activation-form.d.ts +20 -5
  7. package/dist/components/base-activation-form.js +12 -5
  8. package/dist/components/base-activation-form.js.map +1 -1
  9. package/dist/components/base-marketplace-tile.js +12 -10
  10. package/dist/components/base-marketplace-tile.js.map +1 -1
  11. package/dist/components/index.d.ts +1 -1
  12. package/dist/components/index.js.map +1 -1
  13. package/dist/components/oauth-activation-form.d.ts +23 -16
  14. package/dist/components/oauth-activation-form.js +34 -20
  15. package/dist/components/oauth-activation-form.js.map +1 -1
  16. package/dist/components/widgets/use-relative-time.d.ts +7 -0
  17. package/dist/components/widgets/use-relative-time.js +55 -0
  18. package/dist/components/widgets/use-relative-time.js.map +1 -0
  19. package/dist/components/widgets/widget-ask-bar.js.map +1 -1
  20. package/dist/components/widgets/widget-context.d.ts +21 -0
  21. package/dist/components/widgets/widget-context.js.map +1 -1
  22. package/dist/components/widgets/widget-preview-provider.d.ts +1 -1
  23. package/dist/components/widgets/widget-preview-provider.js +1 -1
  24. package/dist/components/widgets/widget-preview-provider.js.map +1 -1
  25. package/dist/components/widgets/widget-state-components.d.ts +4 -1
  26. package/dist/components/widgets/widget-state-components.js +6 -4
  27. package/dist/components/widgets/widget-state-components.js.map +1 -1
  28. package/dist/components/widgets/widget.d.ts +47 -2
  29. package/dist/components/widgets/widget.js +111 -23
  30. package/dist/components/widgets/widget.js.map +1 -1
  31. package/dist/context/egress.js.map +1 -1
  32. package/dist/context/storage-validation.d.ts +24 -4
  33. package/dist/context/storage-validation.js +5 -2
  34. package/dist/context/storage-validation.js.map +1 -1
  35. package/dist/errors/public-action-error.d.ts +31 -0
  36. package/dist/errors/public-action-error.js +35 -0
  37. package/dist/errors/public-action-error.js.map +1 -0
  38. package/dist/hooks/ask-assistant-context.js.map +1 -1
  39. package/dist/hooks/fetch-integration-storage.js.map +1 -1
  40. package/dist/hooks/index.d.ts +2 -0
  41. package/dist/hooks/index.js +6 -0
  42. package/dist/hooks/index.js.map +1 -1
  43. package/dist/hooks/partner-oauth.js +3 -0
  44. package/dist/hooks/partner-oauth.js.map +1 -1
  45. package/dist/hooks/use-integration-action.d.ts +105 -0
  46. package/dist/hooks/use-integration-action.js +134 -0
  47. package/dist/hooks/use-integration-action.js.map +1 -0
  48. package/dist/hooks/use-oauth-connection-status.d.ts +78 -78
  49. package/dist/hooks/use-oauth-connection-status.js +17 -10
  50. package/dist/hooks/use-oauth-connection-status.js.map +1 -1
  51. package/dist/hooks/widget-event-context.js.map +1 -1
  52. package/dist/integration/event-surfaces.js.map +1 -1
  53. package/dist/integration/index.d.ts +3 -2
  54. package/dist/integration/index.js +5 -1
  55. package/dist/integration/index.js.map +1 -1
  56. package/dist/testing/invoke.js +9 -5
  57. package/dist/testing/invoke.js.map +1 -1
  58. package/dist/testing/mock-context.js +8 -7
  59. package/dist/testing/mock-context.js.map +1 -1
  60. package/dist/testing/mock-fetch-provider.js.map +1 -1
  61. package/dist/types/integration.d.ts +42 -24
  62. package/dist/types/integration.js.map +1 -1
  63. package/dist/types/widget-ask-context.d.ts +9 -0
  64. package/dist/types/widget-ask-context.js.map +1 -1
  65. package/dist/types/workspace-target.d.ts +3 -2
  66. package/dist/types/workspace-target.js.map +1 -1
  67. package/eslint.preset.mjs +9 -8
  68. package/package.json +37 -41
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @ekanos/sdk
2
2
 
3
3
  The official SDK for building Ekanos integrations — the packages that add
4
- widgets, assistant tools, and event handlers to a Fusion dashboard.
4
+ widgets, assistant tools, and event handlers to an Ekanos dashboard.
5
5
 
6
6
  **Start here.** This README is the complete authoring reference: everything you
7
7
  can declare, with a worked example for each surface. There is no separate docs
@@ -11,7 +11,7 @@ where to say so.
11
11
  The other packages, so you know when to leave this page: **`@ekanos/cli`** is
12
12
  the toolchain (`init`, `validate`, `dev`, `test`) and owns `ekanos.json`;
13
13
  **`@ekanos/harness`** is the local dev harness that renders your surfaces in
14
- real Fusion chrome; **`@ekanos/ui`** is the component library; and
14
+ real Ekanos chrome; **`@ekanos/ui`** is the component library; and
15
15
  **`@ekanos/integration-schema`** is the validation contract underneath this
16
16
  one. Each has its own README.
17
17
 
@@ -409,7 +409,7 @@ const apiKey = await ctx.secrets.get(ACME_API_KEY);
409
409
 
410
410
  ### `tools`
411
411
 
412
- Tools are what the Fusion assistant (and, where the host allows it, the workflow
412
+ Tools are what the Ekanos assistant (and, where the host allows it, the workflow
413
413
  engine) can call. Each declares a name, a description the model reads, an
414
414
  optional JSON-Schema parameter object, and an async `run`.
415
415
 
@@ -489,7 +489,15 @@ export function CurrentWeatherWidget({ accountId }: IntegrationComponentProps) {
489
489
  const query = useCurrentWeather(accountId, ctx?.state.state === 'active');
490
490
 
491
491
  return (
492
- <Widget.DataState loading={query.isLoading} error={query.isError}>
492
+ <Widget.DataState
493
+ loading={query.isLoading}
494
+ error={query.isError}
495
+ // Optional. They add the header menu's Refresh item, a Try again
496
+ // button on the error view, and an "Updated …" footer.
497
+ onRefresh={() => void query.refetch()}
498
+ refreshing={query.isFetching}
499
+ updatedAt={query.dataUpdatedAt}
500
+ >
493
501
  <Widget.Card>
494
502
  <Widget.Header title="Current conditions" />
495
503
  <Widget.Loading />
@@ -549,7 +557,7 @@ Widget settings live in `ctx.storage`, which is server-side, so a widget reads
549
557
  them back over HTTP with `fetchIntegrationStorage()` from `@ekanos/sdk/hooks` —
550
558
  subject to `clientReadable`, above.
551
559
 
552
- To render a widget anywhere other than the Fusion dashboard — your own app, a
560
+ To render a widget anywhere other than the Ekanos dashboard — your own app, a
553
561
  story, a component test — wrap it in `WidgetPreviewProvider`, which fills the
554
562
  same `WidgetContext` the host fills, with local state and no persistence:
555
563
 
@@ -620,9 +628,17 @@ product — the logo is assigned host-side, not declared here.
620
628
 
621
629
  ### `components.activationForm`
622
630
 
623
- What a user fills in to connect. `BaseActivationForm` owns the dialog chrome,
624
- the submit button, and the error/success states; you supply the
625
- `react-hook-form` instance and the fields.
631
+ What a user fills in to connect — **only** that. The host renders a small
632
+ frame around your form: your product's logo, name and one-line summary, then
633
+ your form. Nothing sits between the two. The host also owns the success
634
+ moment: it closes and toasts, naming the workspace your widgets landed in.
635
+ `BaseActivationForm` renders the action — **Connect** and **Cancel**
636
+ right-aligned under your fields, or, for a form with no fields, a single
637
+ full-width button — plus "Connecting…" while busy and the inline error. You
638
+ supply the `react-hook-form` instance and the fields. Nothing else belongs
639
+ in a form: no title, feature list, permission badge, connected banner or
640
+ success screen. The connect step is kept to the one decision the person
641
+ already made by clicking your product.
626
642
 
627
643
  ```tsx
628
644
  'use client';
@@ -654,7 +670,6 @@ export function AcmePaymentsActivationForm({
654
670
  inline = false, // pass this through — see below
655
671
  }: ActivationFormProps) {
656
672
  const [error, setError] = useState<string | null>(null);
657
- const [success, setSuccess] = useState(false);
658
673
 
659
674
  const form = useForm({ resolver: zodResolver(ActivationSchema),
660
675
  defaultValues: { [ACME_API_KEY]: '', merchantId: '' } });
@@ -670,7 +685,8 @@ export function AcmePaymentsActivationForm({
670
685
  integrationSlug: 'acme-payments',
671
686
  activationData: data,
672
687
  });
673
- setSuccess(true);
688
+ // Report once, straight to the host. It closes and toasts.
689
+ onSuccess?.({ success: true, externalId: data.merchantId as string });
674
690
  } catch (cause) {
675
691
  setError(cause instanceof Error ? cause.message : 'Could not connect.');
676
692
  }
@@ -686,15 +702,6 @@ export function AcmePaymentsActivationForm({
686
702
  onSubmit={handleSubmit}
687
703
  isLoading={isLoading}
688
704
  error={error}
689
- success={success}
690
- successTitle="Acme Payments is connected"
691
- onSuccessClose={() => onSuccess?.({
692
- success: true,
693
- configData: { merchantId: form.getValues('merchantId') },
694
- externalId: form.getValues('merchantId'),
695
- })}
696
- submitButtonText="Connect Acme Payments"
697
- submitButtonLoadingText="Connecting…"
698
705
  >
699
706
  <FormField control={form.control} name={ACME_API_KEY} render={({ field }) => (
700
707
  <FormItem>
@@ -731,36 +738,70 @@ Three things worth knowing:
731
738
  export const ACME_API_KEY = 'acme_api_key';
732
739
  ```
733
740
 
734
- Values you want back on the client instead go in `configData` on the
735
- `ActivationResult` you hand `onSuccess`.
741
+ Values you want persisted as **settings** (not secrets) go in `configData`
742
+ on the `ActivationResult` you hand `onSuccess`, keyed by the `settings/*`
743
+ storage keys you declared. Never put raw field values there: the host
744
+ rejects any key you did not declare, and the user sees a warning that
745
+ "some settings could not be saved".
736
746
  - **`useActivateIntegration` needs the host's injected actions.** It reads them
737
747
  from `IntegrationActivationProvider`, which the host mounts. Outside the host
738
748
  — in the dev harness, or your own app — mount that provider yourself, or the
739
749
  hook has nothing to call.
740
750
 
741
- `OAuthActivationForm` is the connect-button variant for OAuth integrations, and
751
+ `OAuthActivationForm` is the one-button variant for OAuth integrations: it
752
+ renders **Continue with Acme** with a line explaining the user will sign in on
753
+ Acme's site and come straight back, then activates on return. Its `config`
754
+ needs only `slug` and `displayName` (plus `connectPath`, `errorParam` or
755
+ `autoActivateOnConnect: false` when you genuinely need them).
742
756
  `useOAuthConnectionStatus()` reports whether the account is connected.
743
757
 
758
+ Both primitives accept a `footnote` — one quiet line under the action row for
759
+ what happens next — and nothing else decorative.
760
+
761
+ **Reconnecting.** The host mounts your form a second time, with `reconnect`
762
+ set, when a connected integration needs its credentials renewed — after a
763
+ failed health check, or when the owner chooses Reconnect.
764
+ `OAuthActivationForm` handles it for you: the button reads **Reconnect with
765
+ Acme** and always goes through sign-in again, even with a token stored. A
766
+ credential form needs nothing special: it collects new values and activates
767
+ again, which replaces the old ones and resets the integration's health to
768
+ unknown until the next check. A bespoke OAuth form should treat `reconnect`
769
+ the same way — send the person through sign-in rather than skipping it
770
+ because a token exists.
771
+
772
+ **Behavior change for existing OAuth forms:** `OAuthActivationForm`'s
773
+ `children` render only **after** the user has signed in, and only for a form
774
+ with `autoActivateOnConnect: false` is there a moment to see them. They are
775
+ for post-sign-in fields, not for feature lists or permission copy — the
776
+ connect step carries neither. The primitive does not read those
777
+ fields: a form that needs values after sign-in owns its own `useForm` on
778
+ `BaseActivationForm` and saves them before activating (the GitHub form is
779
+ the reference). If your form passed such a child, delete it; the same copy
780
+ belongs in `permissions` / `capabilities`.
781
+
744
782
  A form is optional. Omit it and the host renders a generic one — a plain field
745
783
  per value it needs — rather than nothing.
746
784
 
747
785
  ### `capabilities` and `permissions`
748
786
 
749
- Both are copy for the integration detail page, and both are pure data. **Every
750
- string here is read by the person connecting the integration** — they render on
751
- the activation dialog, not just in a catalogue listing. Write `detail` as a
752
- sentence you would be happy for a customer's security reviewer to read, because
753
- that is who reads it.
787
+ Both are pure data describing what your integration does and touches. They
788
+ are **not** shown on the connect dialog — that step is only your name,
789
+ summary and form. They appear in the platform's product catalog, where a
790
+ source's administrators review an integration before offering it to teams.
791
+ Write `detail` as a sentence you would be happy for a customer's security
792
+ reviewer to read, because that is who reads it. For an OAuth integration,
793
+ make them match the scopes you request: the provider's consent screen is
794
+ what the person connecting actually sees.
754
795
 
755
- Exactly what appears, so you can write to the shape:
796
+ The shape each entry takes:
756
797
 
757
798
  | Section | Renders |
758
799
  |---|---|
759
800
  | **What it does** | `capability.label`, then ` — ` + `capability.description` when present. `capability.icon` is **not** rendered here |
760
801
  | **What it can access** | `permission.label`, a badge reading `Read` or `Write` from `permission.type`, then `permission.detail` on the line below |
761
802
 
762
- Both sections are omitted entirely when their array is empty. `type` drives
763
- only the badge — it does not reorder or group anything.
803
+ An empty array simply declares nothing. `type` drives only the Read/Write
804
+ label — it does not reorder or group anything.
764
805
 
765
806
  ```ts
766
807
  capabilities: [
@@ -810,7 +851,8 @@ export const paymentUpdatedWebhook: PartnerWebhookDeclaration<MyStorage> = {
810
851
  payloadSchema: PaymentUpdatedPayloadSchema,
811
852
 
812
853
  // How the TRANSPORT verifies. `'none'` states explicitly that the source
813
- // is unsigned. Your handler never checks a signature.
854
+ // is unsigned. Your handler never checks a signature. Add `scheme` when the
855
+ // provider is not a plain HMAC-SHA256 digest of the body — see below.
814
856
  signature: { header: 'x-acme-signature', secretName: 'acme_webhook_secret' },
815
857
 
816
858
  // Seeds the harness's payload editor and documents the shape beside the
@@ -871,6 +913,44 @@ Signature verification is the transport's job in every environment — the host
871
913
  ingress enforces it, the local harness logs it as skipped. Do not verify it
872
914
  yourself; you would be verifying a header the transport already consumed.
873
915
 
916
+ #### `signature.scheme` — when the provider is not a plain body digest
917
+
918
+ Without `scheme`, the transport expects the header to be an HMAC-SHA256 of the
919
+ raw body, hex or base64, optionally labelled `sha256=` (GitHub, Linear).
920
+ Most other providers differ in one of three ways: they
921
+ pack the digest into `k=v` pairs, they sign a timestamp along with the body, or
922
+ they use SHA-1. `scheme` describes that declaratively; the transport does the
923
+ arithmetic and your handler still contains no verification code.
924
+
925
+ | Provider | `scheme` |
926
+ |---|---|
927
+ | Stripe, Calendly (`t=…,v1=…`, HMAC over `"<t>.<body>"`) | `{ format: 'keyed', digestKey: 'v1', timestampKey: 't', signedPayload: '{timestamp}.{body}', toleranceSeconds: 300 }` |
928
+ | Retell (`v=<unix_ms>,d=<hex>`, HMAC over `body + timestamp`) | `{ format: 'keyed', digestKey: 'd', timestampKey: 'v', signedPayload: '{body}{timestamp}' }` |
929
+ | Intercom (`X-Hub-Signature: sha1=…`) | `{ algorithm: 'sha1' }` |
930
+ | Svix-style single signature (`svix-timestamp` header, HMAC over `"<ts>.<body>"`) | `{ timestampHeader: 'svix-timestamp', signedPayload: '{timestamp}.{body}' }` |
931
+
932
+ Fields: `algorithm` (`'sha256'` default, or `'sha1'`), `format` (`'digest'`
933
+ default, or `'keyed'` for `k=v,k=v` headers), `digestKey` / `timestampKey`
934
+ (which pairs, keyed only), `timestampHeader` (a separate header instead of a
935
+ pair), `signedPayload` (a template over `{body}` and `{timestamp}`; default
936
+ `'{body}'`), `toleranceSeconds` (replay window whenever a timestamp source is
937
+ declared; default 300, `0` disables). Seconds and milliseconds are both
938
+ accepted from the sender. `defineIntegration()` rejects a scheme that cannot be
939
+ verified — a keyed format with no `digestKey`, a `{timestamp}` with no source,
940
+ a timestamp source whose value is **not** in `signedPayload` (a replay window
941
+ over an unsigned timestamp protects nothing), a transport header name such as
942
+ `authorization` — so the mistake surfaces before the first delivery, not as a
943
+ 401 on it. Linear's replay guard is a field inside the signed body, so for
944
+ Linear you omit `scheme` and check `webhookTimestamp` in the handler.
945
+
946
+ Two things the scheme never does: it never takes the algorithm from the
947
+ header (a `sha1=` label under a `sha256` declaration fails), and it never lets
948
+ a stale delivery through because the digest was right — freshness is checked
949
+ first. Svix-style multi-signature headers (`v1,<b64> v1,<b64>`, space-separated
950
+ with a `whsec_` base64 secret) are not covered yet; declare `'none'` for those
951
+ and say so in the description. Slack (`v0=` label over `v0:{ts}:{body}`) is
952
+ not covered either.
953
+
874
954
  ### `schedules`
875
955
 
876
956
  A cron-driven run. Same handler shape, same `ctx`.
@@ -1031,7 +1111,7 @@ either be ignored or aim the host's callback somewhere it should not go.
1031
1111
  Register this with your provider, against whichever host will run the flow:
1032
1112
 
1033
1113
  ```
1034
- https://<your-fusion-host>/api/integrations/<your-slug>/callback
1114
+ https://<your-ekanos-host>/api/integrations/<your-slug>/callback
1035
1115
  ```
1036
1116
 
1037
1117
  The dev harness does not serve that route, so you cannot complete a round trip
@@ -1189,7 +1269,7 @@ and the host adapts the definition internally.
1189
1269
 
1190
1270
  ### Styling
1191
1271
 
1192
- `@ekanos/ui` components emit Tailwind class strings against Fusion's semantic
1272
+ `@ekanos/ui` components emit Tailwind class strings against Ekanos's semantic
1193
1273
  tokens, and icons are Font Awesome glyphs the host loads. Import
1194
1274
  `@ekanos/ui/styles.css` (or the narrower `tokens.css` / `theme.css` /
1195
1275
  `base.css`) to get the token layer — that also draws a `□` placeholder for
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: **160** across 7 entrypoints.
12
+ Total exported names: **171** across 7 entrypoints.
13
13
 
14
14
  ## `@ekanos/sdk` (32 exports)
15
15
 
@@ -48,7 +48,7 @@ Total exported names: **160** across 7 entrypoints.
48
48
  | `WidgetSuggestedPrompt` | interface |
49
49
  | `WorkspaceTargetDefinition` | interface |
50
50
 
51
- ## `@ekanos/sdk/components` (24 exports)
51
+ ## `@ekanos/sdk/components` (25 exports)
52
52
 
53
53
  | Export | Kind |
54
54
  | --- | --- |
@@ -68,6 +68,7 @@ Total exported names: **160** across 7 entrypoints.
68
68
  | `WidgetAskBar` | function |
69
69
  | `WidgetContext` | variable |
70
70
  | `WidgetContextValue` | interface |
71
+ | `WidgetData` | interface |
71
72
  | `WidgetDataLoading` | function |
72
73
  | `WidgetHealth` | interface |
73
74
  | `WidgetMeta` | interface |
@@ -77,7 +78,7 @@ Total exported names: **160** across 7 entrypoints.
77
78
  | `WidgetState` | interface |
78
79
  | `currentReturnPath` | function |
79
80
 
80
- ## `@ekanos/sdk/hooks` (37 exports)
81
+ ## `@ekanos/sdk/hooks` (43 exports)
81
82
 
82
83
  | Export | Kind |
83
84
  | --- | --- |
@@ -89,6 +90,9 @@ Total exported names: **160** across 7 entrypoints.
89
90
  | `DeactivateIntegrationAction` | type alias |
90
91
  | `DeactivateIntegrationInput` | interface |
91
92
  | `FetchIntegrationStorageOptions` | interface |
93
+ | `IntegrationActionError` | class |
94
+ | `IntegrationActionErrorCode` | type alias |
95
+ | `IntegrationActionErrorDetails` | interface |
92
96
  | `IntegrationActionResult` | type alias |
93
97
  | `IntegrationActivationActions` | interface |
94
98
  | `IntegrationActivationDataProvider` | function |
@@ -97,6 +101,7 @@ Total exported names: **160** across 7 entrypoints.
97
101
  | `OAuthErrorParam` | interface |
98
102
  | `PartnerOAuthPathOptions` | interface |
99
103
  | `PublishEventInput` | interface |
104
+ | `UseIntegrationActionOptions` | interface |
100
105
  | `UseOAuthConnectionStatusOptions` | interface |
101
106
  | `WidgetEvent` | interface |
102
107
  | `WidgetEventFilter` | interface |
@@ -104,6 +109,7 @@ Total exported names: **160** across 7 entrypoints.
104
109
  | `WidgetEventSubscriber` | type alias |
105
110
  | `createPartnerConnectionCheck` | function |
106
111
  | `fetchIntegrationStorage` | function |
112
+ | `integrationActionUrl` | function |
107
113
  | `integrationStorageUrl` | function |
108
114
  | `partnerOAuthConnectPath` | function |
109
115
  | `partnerOAuthErrorParam` | function |
@@ -112,6 +118,7 @@ Total exported names: **160** across 7 entrypoints.
112
118
  | `useActivationData` | function |
113
119
  | `useAskAssistant` | function |
114
120
  | `useDeactivateIntegration` | function |
121
+ | `useIntegrationAction` | function |
115
122
  | `useIntegrationFetch` | function |
116
123
  | `useOAuthConnectionStatus` | function |
117
124
  | `useOAuthWidgetGate` | function |
@@ -184,10 +191,11 @@ Total exported names: **160** across 7 entrypoints.
184
191
  | `invokeSchedule` | function |
185
192
  | `invokeWebhook` | function |
186
193
 
187
- ## `@ekanos/sdk/integration` (26 exports)
194
+ ## `@ekanos/sdk/integration` (30 exports)
188
195
 
189
196
  | Export | Kind |
190
197
  | --- | --- |
198
+ | `DEFAULT_SIGNATURE_TOLERANCE_SECONDS` | variable |
191
199
  | `DefinitionCollisionInput` | interface |
192
200
  | `FirstPartyInventory` | interface |
193
201
  | `IntegrationCapabilityDeclaration` | type alias |
@@ -199,18 +207,21 @@ Total exported names: **160** across 7 entrypoints.
199
207
  | `OAuthProviderDeclaration` | interface |
200
208
  | `OAuthTokens` | interface |
201
209
  | `OnActivateHandler` | type alias |
210
+ | `PartnerActionDeclaration` | interface |
202
211
  | `PartnerOAuthDeclaration` | interface |
203
212
  | `PartnerScheduleDeclaration` | interface |
204
213
  | `PartnerToolModule` | interface |
205
214
  | `PartnerToolParameters` | type alias |
206
215
  | `PartnerWebhookDeclaration` | interface |
207
216
  | `PartnerWidgetDeclaration` | type alias |
217
+ | `PublicActionError` | class |
208
218
  | `ScheduleInvocation` | interface |
209
219
  | `ScheduleResult` | interface |
210
220
  | `ToolClassificationProposal` | type alias |
211
221
  | `WebhookEvent` | interface |
212
222
  | `WebhookResult` | interface |
213
223
  | `WebhookSignatureDeclaration` | type alias |
224
+ | `WebhookSignatureScheme` | interface |
214
225
  | `defineIntegration` | function |
215
226
  | `parseCronExpression` | function |
216
227
  | `validateIntegrationDefinitions` | function |
@@ -16,15 +16,25 @@ interface BaseActivationDialogProps {
16
16
  inline?: boolean;
17
17
  }
18
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, }: {
19
+ /**
20
+ * The form's body when it sits inside chrome the HOST owns (the connect
21
+ * dialog, the workspace notice, first-use). The host already shows the
22
+ * integration's name, what it does and what it can access, and it owns
23
+ * the success moment (it closes and toasts) — so this renders the fields,
24
+ * the error, and nothing else.
25
+ *
26
+ * `success` is a hand-off, not a screen: the moment it turns true,
27
+ * `onSuccessClose` runs once, which is what lets a form written for the
28
+ * old "Continue" panel finish the same way as one that reports success
29
+ * directly. Nothing renders after it fires; the host takes over.
30
+ */
31
+ export declare function ActivationInline({ isLoading, error, success, onSuccessClose, children, }: {
20
32
  isLoading?: boolean;
21
33
  error?: string | null;
22
34
  success?: boolean;
23
- successTitle?: string;
24
- successMessage?: string;
25
- onClose: () => void;
35
+ onSuccessClose?: () => void;
26
36
  children: ReactNode;
27
- }): import("react").JSX.Element;
37
+ }): import("react").JSX.Element | null;
28
38
  export declare function ActivationDialog({ isOpen, onClose, title, description, children, isLoading, error, success, successTitle, successMessage, maxWidth, }: {
29
39
  isOpen: boolean;
30
40
  onClose: () => void;
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from 'react';
3
4
  import { Alert, AlertDescription } from '@ekanos/ui/alert';
4
5
  import { Button } from '@ekanos/ui/button';
5
6
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@ekanos/ui/dialog';
@@ -12,10 +13,10 @@ function SuccessContent({ successTitle = 'Integration Activated!', successMessag
12
13
  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
  }
14
15
  function ErrorAlert({ error }) {
15
- return (_jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: error }) }));
16
+ return (_jsx(Alert, { variant: "destructive", role: "alert", children: _jsx(AlertDescription, { children: error }) }));
16
17
  }
17
18
  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
+ 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-third", className: "text-primary h-8 w-8 animate-spin" }), _jsx("span", { className: "text-muted-foreground text-sm", children: "Loading..." })] }) }));
19
20
  }
20
21
  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
22
  const handleClose = () => {
@@ -27,13 +28,38 @@ export function BaseActivationDialog({ isOpen, onClose, title, description, chil
27
28
  }
28
29
  };
29
30
  if (inline) {
30
- return (_jsx(ActivationInline, { isLoading: isLoading, error: error, success: success, successTitle: successTitle, successMessage: successMessage, onClose: handleClose, children: children }));
31
+ return (_jsx(ActivationInline, { isLoading: isLoading, error: error, success: success, onSuccessClose: success ? handleClose : undefined, children: children }));
31
32
  }
32
33
  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
  }
34
35
  // --- 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 }) })] }));
36
+ /**
37
+ * The form's body when it sits inside chrome the HOST owns (the connect
38
+ * dialog, the workspace notice, first-use). The host already shows the
39
+ * integration's name, what it does and what it can access, and it owns
40
+ * the success moment (it closes and toasts) — so this renders the fields,
41
+ * the error, and nothing else.
42
+ *
43
+ * `success` is a hand-off, not a screen: the moment it turns true,
44
+ * `onSuccessClose` runs once, which is what lets a form written for the
45
+ * old "Continue" panel finish the same way as one that reports success
46
+ * directly. Nothing renders after it fires; the host takes over.
47
+ */
48
+ export function ActivationInline({ isLoading = false, error = null, success = false, onSuccessClose, children, }) {
49
+ const handedOff = useRef(false);
50
+ // Hands the success state to the host exactly once. An effect is the
51
+ // right tool here: this reacts to a state transition that the form's own
52
+ // submit handler set, and the host callback it calls navigates.
53
+ useEffect(() => {
54
+ if (!success || !onSuccessClose || handedOff.current)
55
+ return;
56
+ handedOff.current = true;
57
+ onSuccessClose();
58
+ }, [success, onSuccessClose]);
59
+ if (success) {
60
+ return null;
61
+ }
62
+ return (_jsxs("div", { className: cn('flex flex-col gap-4', isLoading && 'opacity-70'), "aria-busy": isLoading, children: [_jsx(If, { condition: !!error, children: _jsx(ErrorAlert, { error: error }) }), children] }));
37
63
  }
38
64
  export function ActivationDialog({ isOpen, onClose, title, description, children, isLoading = false, error = null, success = false, successTitle, successMessage, maxWidth = 'sm:max-w-[600px]', }) {
39
65
  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, {})] }) }));
@@ -1 +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"]}
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, useEffect, useRef } 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\" role=\"alert\">\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-third\"\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 onSuccessClose={success ? handleClose : undefined}\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\n/**\n * The form's body when it sits inside chrome the HOST owns (the connect\n * dialog, the workspace notice, first-use). The host already shows the\n * integration's name, what it does and what it can access, and it owns\n * the success moment (it closes and toasts) — so this renders the fields,\n * the error, and nothing else.\n *\n * `success` is a hand-off, not a screen: the moment it turns true,\n * `onSuccessClose` runs once, which is what lets a form written for the\n * old \"Continue\" panel finish the same way as one that reports success\n * directly. Nothing renders after it fires; the host takes over.\n */\nexport function ActivationInline({\n isLoading = false,\n error = null,\n success = false,\n onSuccessClose,\n children,\n}: {\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n onSuccessClose?: () => void;\n children: ReactNode;\n}) {\n const handedOff = useRef(false);\n\n // Hands the success state to the host exactly once. An effect is the\n // right tool here: this reacts to a state transition that the form's own\n // submit handler set, and the host callback it calls navigates.\n useEffect(() => {\n if (!success || !onSuccessClose || handedOff.current) return;\n\n handedOff.current = true;\n onSuccessClose();\n }, [success, onSuccessClose]);\n\n if (success) {\n return null;\n }\n\n return (\n <div\n className={cn('flex flex-col gap-4', isLoading && 'opacity-70')}\n aria-busy={isLoading}\n >\n <If condition={!!error}>\n <ErrorAlert error={error!} />\n </If>\n {children}\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"]}
@@ -3,7 +3,9 @@ import { FieldValues, UseFormReturn } from 'react-hook-form';
3
3
  export interface BaseActivationFormProps<TFieldValues extends FieldValues = FieldValues> {
4
4
  isOpen: boolean;
5
5
  onClose: () => void;
6
+ /** Shown only when NOT inline. Inline, the host names the integration. */
6
7
  title: string;
8
+ /** Shown only when NOT inline. */
7
9
  description: string;
8
10
  /**
9
11
  * Generic rather than `UseFormReturn<any>`: with current react-hook-form
@@ -22,15 +24,28 @@ export interface BaseActivationFormProps<TFieldValues extends FieldValues = Fiel
22
24
  successTitle?: string;
23
25
  successMessage?: string;
24
26
  onSuccessClose?: () => void;
25
- submitButtonText?: string;
26
- submitButtonLoadingText?: string;
27
+ /** Defaults to "Connect" — the one verb every integration shares. */
28
+ submitButtonText?: ReactNode;
29
+ /** Defaults to "Connecting…". */
30
+ submitButtonLoadingText?: ReactNode;
27
31
  submitButtonDisabled?: boolean;
28
- /** @deprecated No longer needed — cancel button is hidden by default */
32
+ /**
33
+ * Inline with fields, a Cancel button sits beside Connect and calls
34
+ * `onClose`; pass `false` for a surface that supplies its own way out.
35
+ * A form with no fields never shows one — its single button spans the
36
+ * frame and the surrounding dialog's close is the way out.
37
+ */
29
38
  showCancelButton?: boolean;
30
- /** @deprecated No longer needed — submit button is centered by default */
39
+ /** @deprecated The action row is right-aligned; this is ignored. */
31
40
  centerSubmitButton?: boolean;
41
+ /**
42
+ * One quiet line under the action row — what happens next ("You'll sign
43
+ * in on QuickBooks' site and come back here."). Nothing else belongs
44
+ * there: not features, not permissions, the host shows those.
45
+ */
46
+ footnote?: ReactNode;
32
47
  maxWidth?: string;
33
48
  /** Render inline instead of in a dialog */
34
49
  inline?: boolean;
35
50
  }
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;
51
+ export declare function BaseActivationForm<TFieldValues extends FieldValues = FieldValues>({ isOpen, onClose, title, description, form, onSubmit, children, isLoading, error, success, successTitle, successMessage, onSuccessClose, submitButtonText, submitButtonLoadingText, submitButtonDisabled, showCancelButton, footnote, maxWidth, inline, }: BaseActivationFormProps<TFieldValues>): import("react").JSX.Element;
@@ -1,11 +1,12 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTransition } from 'react';
4
4
  import { Button } from '@ekanos/ui/button';
5
5
  import { Form } from '@ekanos/ui/form';
6
6
  import { Icon } from '@ekanos/ui/icon';
7
+ import { Trans } from '@ekanos/ui/trans';
7
8
  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
+ export function BaseActivationForm({ isOpen, onClose, title, description, form, onSubmit, children, isLoading = false, error = null, success = false, successTitle, successMessage, onSuccessClose, submitButtonText, submitButtonLoadingText, submitButtonDisabled = false, showCancelButton = true, footnote, maxWidth, inline = false, }) {
9
10
  const [pending, startTransition] = useTransition();
10
11
  const loading = pending || isLoading;
11
12
  const handleSubmit = (data) => {
@@ -13,9 +14,15 @@ export function BaseActivationForm({ isOpen, onClose, title, description, form,
13
14
  await onSubmit(data);
14
15
  });
15
16
  };
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] }) })] }) })));
17
+ // `children` is null for a form with nothing to fill in (an OAuth button,
18
+ // a one-click connect), and the action row is then the whole form —
19
+ // no empty field block above it.
20
+ const hasFields = children !== null && children !== undefined;
21
+ const formContent = (_jsx(Form, Object.assign({}, form, { children: _jsxs("form", { onSubmit: form.handleSubmit(handleSubmit), className: "flex flex-col gap-5", "data-test": "activation-form", children: [hasFields ? (_jsx("fieldset", { className: "flex min-w-0 flex-col gap-5", disabled: loading, children: children })) : null, _jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: hasFields
22
+ ? 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end'
23
+ : 'flex flex-col', children: [inline && showCancelButton && hasFields ? (_jsx(Button, { type: "button", variant: "ghost", onClick: onClose, disabled: loading, "data-test": "activation-cancel", children: _jsx(Trans, { i18nKey: "common:cancel", defaults: "Cancel" }) })) : null, _jsx(Button, { type: "submit", size: hasFields ? 'default' : 'lg', className: hasFields ? undefined : 'w-full', disabled: loading || submitButtonDisabled, "data-test": "activation-submit", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Icon, { name: "fa-light fa-spinner-third", className: "h-4 w-4 animate-spin" }), submitButtonLoadingText !== null && submitButtonLoadingText !== void 0 ? submitButtonLoadingText : (_jsx(Trans, { i18nKey: "common:integrationConnect.connecting", defaults: "Connecting\u2026" }))] })) : ((submitButtonText !== null && submitButtonText !== void 0 ? submitButtonText : (_jsx(Trans, { i18nKey: "common:integrationConnect.connect", defaults: "Connect" })))) })] }), footnote ? (_jsx("p", { className: hasFields
24
+ ? 'text-muted-foreground text-sm sm:text-right'
25
+ : 'text-muted-foreground text-center text-sm', children: footnote })) : null] })] }) })));
19
26
  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
27
  }
21
28
  //# sourceMappingURL=base-activation-form.js.map