@ekanos/sdk 0.3.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.
- package/README.md +113 -33
- package/api-report.md +7 -3
- package/dist/components/base-activation-dialog.d.ts +15 -5
- package/dist/components/base-activation-dialog.js +31 -5
- package/dist/components/base-activation-dialog.js.map +1 -1
- package/dist/components/base-activation-form.d.ts +20 -5
- package/dist/components/base-activation-form.js +12 -5
- package/dist/components/base-activation-form.js.map +1 -1
- package/dist/components/base-marketplace-tile.js +12 -10
- package/dist/components/base-marketplace-tile.js.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/oauth-activation-form.d.ts +23 -16
- package/dist/components/oauth-activation-form.js +34 -20
- package/dist/components/oauth-activation-form.js.map +1 -1
- package/dist/components/widgets/use-relative-time.d.ts +7 -0
- package/dist/components/widgets/use-relative-time.js +55 -0
- package/dist/components/widgets/use-relative-time.js.map +1 -0
- package/dist/components/widgets/widget-ask-bar.js.map +1 -1
- package/dist/components/widgets/widget-context.d.ts +21 -0
- package/dist/components/widgets/widget-context.js.map +1 -1
- package/dist/components/widgets/widget-preview-provider.d.ts +1 -1
- package/dist/components/widgets/widget-preview-provider.js +1 -1
- package/dist/components/widgets/widget-preview-provider.js.map +1 -1
- package/dist/components/widgets/widget-state-components.d.ts +4 -1
- package/dist/components/widgets/widget-state-components.js +6 -4
- package/dist/components/widgets/widget-state-components.js.map +1 -1
- package/dist/components/widgets/widget.d.ts +47 -2
- package/dist/components/widgets/widget.js +111 -23
- package/dist/components/widgets/widget.js.map +1 -1
- package/dist/context/egress.js.map +1 -1
- package/dist/context/storage-validation.d.ts +24 -4
- package/dist/context/storage-validation.js +5 -2
- package/dist/context/storage-validation.js.map +1 -1
- package/dist/hooks/ask-assistant-context.js.map +1 -1
- package/dist/hooks/fetch-integration-storage.js.map +1 -1
- package/dist/hooks/partner-oauth.js +3 -0
- package/dist/hooks/partner-oauth.js.map +1 -1
- package/dist/hooks/use-integration-action.js.map +1 -1
- package/dist/hooks/use-oauth-connection-status.d.ts +78 -78
- package/dist/hooks/use-oauth-connection-status.js +17 -10
- package/dist/hooks/use-oauth-connection-status.js.map +1 -1
- package/dist/hooks/widget-event-context.js.map +1 -1
- package/dist/integration/event-surfaces.js.map +1 -1
- package/dist/integration/index.d.ts +2 -2
- package/dist/integration/index.js +1 -1
- package/dist/integration/index.js.map +1 -1
- package/dist/testing/invoke.js +9 -5
- package/dist/testing/invoke.js.map +1 -1
- package/dist/testing/mock-context.js +8 -7
- package/dist/testing/mock-context.js.map +1 -1
- package/dist/testing/mock-fetch-provider.js.map +1 -1
- package/dist/types/integration.d.ts +27 -21
- package/dist/types/integration.js.map +1 -1
- package/dist/types/widget-ask-context.d.ts +9 -0
- package/dist/types/widget-ask-context.js.map +1 -1
- package/dist/types/workspace-target.d.ts +3 -2
- package/dist/types/workspace-target.js.map +1 -1
- package/eslint.preset.mjs +9 -8
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
|
|
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
|
-
|
|
763
|
-
|
|
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-
|
|
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
|
|
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: **
|
|
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: **167** across 7 entrypoints.
|
|
|
48
48
|
| `WidgetSuggestedPrompt` | interface |
|
|
49
49
|
| `WorkspaceTargetDefinition` | interface |
|
|
50
50
|
|
|
51
|
-
## `@ekanos/sdk/components` (
|
|
51
|
+
## `@ekanos/sdk/components` (25 exports)
|
|
52
52
|
|
|
53
53
|
| Export | Kind |
|
|
54
54
|
| --- | --- |
|
|
@@ -68,6 +68,7 @@ Total exported names: **167** 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 |
|
|
@@ -190,10 +191,11 @@ Total exported names: **167** across 7 entrypoints.
|
|
|
190
191
|
| `invokeSchedule` | function |
|
|
191
192
|
| `invokeWebhook` | function |
|
|
192
193
|
|
|
193
|
-
## `@ekanos/sdk/integration` (
|
|
194
|
+
## `@ekanos/sdk/integration` (30 exports)
|
|
194
195
|
|
|
195
196
|
| Export | Kind |
|
|
196
197
|
| --- | --- |
|
|
198
|
+
| `DEFAULT_SIGNATURE_TOLERANCE_SECONDS` | variable |
|
|
197
199
|
| `DefinitionCollisionInput` | interface |
|
|
198
200
|
| `FirstPartyInventory` | interface |
|
|
199
201
|
| `IntegrationCapabilityDeclaration` | type alias |
|
|
@@ -205,6 +207,7 @@ Total exported names: **167** across 7 entrypoints.
|
|
|
205
207
|
| `OAuthProviderDeclaration` | interface |
|
|
206
208
|
| `OAuthTokens` | interface |
|
|
207
209
|
| `OnActivateHandler` | type alias |
|
|
210
|
+
| `PartnerActionDeclaration` | interface |
|
|
208
211
|
| `PartnerOAuthDeclaration` | interface |
|
|
209
212
|
| `PartnerScheduleDeclaration` | interface |
|
|
210
213
|
| `PartnerToolModule` | interface |
|
|
@@ -218,6 +221,7 @@ Total exported names: **167** across 7 entrypoints.
|
|
|
218
221
|
| `WebhookEvent` | interface |
|
|
219
222
|
| `WebhookResult` | interface |
|
|
220
223
|
| `WebhookSignatureDeclaration` | type alias |
|
|
224
|
+
| `WebhookSignatureScheme` | interface |
|
|
221
225
|
| `defineIntegration` | function |
|
|
222
226
|
| `parseCronExpression` | function |
|
|
223
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-activation-form.js","sourceRoot":"","sources":["../../src/components/base-activation-form.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAa,aAAa,EAAE,MAAM,OAAO,CAAC;AAEjD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"base-activation-form.js","sourceRoot":"","sources":["../../src/components/base-activation-form.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { ReactNode, useTransition } from 'react';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Form } from '@ekanos/ui/form';\nimport { Icon } from '@ekanos/ui/icon';\nimport { Trans } from '@ekanos/ui/trans';\nimport { FieldValues, UseFormReturn } from 'react-hook-form';\n\nimport { BaseActivationDialog } from './base-activation-dialog';\n\nexport interface BaseActivationFormProps<\n TFieldValues extends FieldValues = FieldValues,\n> {\n isOpen: boolean;\n onClose: () => void;\n /** Shown only when NOT inline. Inline, the host names the integration. */\n title: string;\n /** Shown only when NOT inline. */\n description: string;\n /**\n * Generic rather than `UseFormReturn<any>`: with current react-hook-form\n * typings a concrete `UseFormReturn<T>` is NOT assignable to\n * `UseFormReturn<any>` (the `control._options.validate` parameter is\n * checked contravariantly), which broke every out-of-repo consumer — the\n * clean-room pack test caught it. Inference from the `form` prop keeps\n * every call site source-compatible.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n form: UseFormReturn<TFieldValues, any, any>;\n onSubmit: (data: TFieldValues) => Promise<void> | void;\n children: ReactNode;\n isLoading?: boolean;\n error?: string | null;\n success?: boolean;\n successTitle?: string;\n successMessage?: string;\n onSuccessClose?: () => void;\n /** Defaults to \"Connect\" — the one verb every integration shares. */\n submitButtonText?: ReactNode;\n /** Defaults to \"Connecting…\". */\n submitButtonLoadingText?: ReactNode;\n submitButtonDisabled?: boolean;\n /**\n * Inline with fields, a Cancel button sits beside Connect and calls\n * `onClose`; pass `false` for a surface that supplies its own way out.\n * A form with no fields never shows one — its single button spans the\n * frame and the surrounding dialog's close is the way out.\n */\n showCancelButton?: boolean;\n /** @deprecated The action row is right-aligned; this is ignored. */\n centerSubmitButton?: boolean;\n /**\n * One quiet line under the action row — what happens next (\"You'll sign\n * in on QuickBooks' site and come back here.\"). Nothing else belongs\n * there: not features, not permissions, the host shows those.\n */\n footnote?: ReactNode;\n maxWidth?: string;\n /** Render inline instead of in a dialog */\n inline?: boolean;\n}\n\nexport function BaseActivationForm<\n TFieldValues extends FieldValues = FieldValues,\n>({\n isOpen,\n onClose,\n title,\n description,\n form,\n onSubmit,\n children,\n isLoading = false,\n error = null,\n success = false,\n successTitle,\n successMessage,\n onSuccessClose,\n submitButtonText,\n submitButtonLoadingText,\n submitButtonDisabled = false,\n showCancelButton = true,\n footnote,\n maxWidth,\n inline = false,\n}: BaseActivationFormProps<TFieldValues>) {\n const [pending, startTransition] = useTransition();\n const loading = pending || isLoading;\n\n const handleSubmit = (data: TFieldValues) => {\n startTransition(async () => {\n await onSubmit(data);\n });\n };\n\n // `children` is null for a form with nothing to fill in (an OAuth button,\n // a one-click connect), and the action row is then the whole form —\n // no empty field block above it.\n const hasFields = children !== null && children !== undefined;\n\n const formContent = (\n <Form {...form}>\n <form\n onSubmit={form.handleSubmit(handleSubmit)}\n className=\"flex flex-col gap-5\"\n data-test=\"activation-form\"\n >\n {hasFields ? (\n <fieldset className=\"flex min-w-0 flex-col gap-5\" disabled={loading}>\n {children}\n </fieldset>\n ) : null}\n\n {/*\n Two shapes. With fields: Cancel + Connect, right-aligned, under\n what was filled in. Without (a sign-in button, a one-click\n connect): the button IS the form, so it spans the frame and needs\n no Cancel of its own — the dialog's close does that job.\n */}\n <div className=\"flex flex-col gap-2\">\n <div\n className={\n hasFields\n ? 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end'\n : 'flex flex-col'\n }\n >\n {inline && showCancelButton && hasFields ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n onClick={onClose}\n disabled={loading}\n data-test=\"activation-cancel\"\n >\n <Trans i18nKey=\"common:cancel\" defaults=\"Cancel\" />\n </Button>\n ) : null}\n\n <Button\n type=\"submit\"\n size={hasFields ? 'default' : 'lg'}\n className={hasFields ? undefined : 'w-full'}\n disabled={loading || submitButtonDisabled}\n data-test=\"activation-submit\"\n >\n {loading ? (\n <>\n <Icon\n name=\"fa-light fa-spinner-third\"\n className=\"h-4 w-4 animate-spin\"\n />\n {submitButtonLoadingText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.connecting\"\n defaults=\"Connecting…\"\n />\n )}\n </>\n ) : (\n (submitButtonText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.connect\"\n defaults=\"Connect\"\n />\n ))\n )}\n </Button>\n </div>\n\n {footnote ? (\n <p\n className={\n hasFields\n ? 'text-muted-foreground text-sm sm:text-right'\n : 'text-muted-foreground text-center text-sm'\n }\n >\n {footnote}\n </p>\n ) : null}\n </div>\n </form>\n </Form>\n );\n\n return (\n <BaseActivationDialog\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n description={description}\n isLoading={loading}\n error={error}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n onSuccessClose={onSuccessClose}\n maxWidth={maxWidth}\n inline={inline}\n >\n {formContent}\n </BaseActivationDialog>\n );\n}\n"]}
|