@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.
- package/README.md +113 -33
- package/api-report.md +15 -4
- 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/errors/public-action-error.d.ts +31 -0
- package/dist/errors/public-action-error.js +35 -0
- package/dist/errors/public-action-error.js.map +1 -0
- package/dist/hooks/ask-assistant-context.js.map +1 -1
- package/dist/hooks/fetch-integration-storage.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.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.d.ts +105 -0
- package/dist/hooks/use-integration-action.js +134 -0
- package/dist/hooks/use-integration-action.js.map +1 -0
- 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 +3 -2
- package/dist/integration/index.js +5 -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 +42 -24
- 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
|
@@ -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":"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;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAGzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAsDhE,MAAM,UAAU,kBAAkB,CAEhC,EACA,MAAM,EACN,OAAO,EACP,KAAK,EACL,WAAW,EACX,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,SAAS,GAAG,KAAK,EACjB,KAAK,GAAG,IAAI,EACZ,OAAO,GAAG,KAAK,EACf,YAAY,EACZ,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,uBAAuB,EACvB,oBAAoB,GAAG,KAAK,EAC5B,gBAAgB,GAAG,IAAI,EACvB,QAAQ,EACR,QAAQ,EACR,MAAM,GAAG,KAAK,GACwB;IACtC,MAAM,CAAC,OAAO,EAAE,eAAe,CAAC,GAAG,aAAa,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,OAAO,IAAI,SAAS,CAAC;IAErC,MAAM,YAAY,GAAG,CAAC,IAAkB,EAAE,EAAE;QAC1C,eAAe,CAAC,KAAK,IAAI,EAAE;YACzB,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,0EAA0E;IAC1E,oEAAoE;IACpE,iCAAiC;IACjC,MAAM,SAAS,GAAG,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS,CAAC;IAE9D,MAAM,WAAW,GAAG,CAClB,KAAC,IAAI,oBAAK,IAAI,cACZ,gBACE,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,EACzC,SAAS,EAAC,qBAAqB,eACrB,iBAAiB,aAE1B,SAAS,CAAC,CAAC,CAAC,CACX,mBAAU,SAAS,EAAC,6BAA6B,EAAC,QAAQ,EAAE,OAAO,YAChE,QAAQ,GACA,CACZ,CAAC,CAAC,CAAC,IAAI,EAQR,eAAK,SAAS,EAAC,qBAAqB,aAClC,eACE,SAAS,EACP,SAAS;gCACP,CAAC,CAAC,wDAAwD;gCAC1D,CAAC,CAAC,eAAe,aAGpB,MAAM,IAAI,gBAAgB,IAAI,SAAS,CAAC,CAAC,CAAC,CACzC,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,OAAO,EACf,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,OAAO,eACP,mBAAmB,YAE7B,KAAC,KAAK,IAAC,OAAO,EAAC,eAAe,EAAC,QAAQ,EAAC,QAAQ,GAAG,GAC5C,CACV,CAAC,CAAC,CAAC,IAAI,EAER,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAClC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAC3C,QAAQ,EAAE,OAAO,IAAI,oBAAoB,eAC/B,mBAAmB,YAE5B,OAAO,CAAC,CAAC,CAAC,CACT,8BACE,KAAC,IAAI,IACH,IAAI,EAAC,2BAA2B,EAChC,SAAS,EAAC,sBAAsB,GAChC,EACD,uBAAuB,aAAvB,uBAAuB,cAAvB,uBAAuB,GAAI,CAC1B,KAAC,KAAK,IACJ,OAAO,EAAC,sCAAsC,EAC9C,QAAQ,EAAC,kBAAa,GACtB,CACH,IACA,CACJ,CAAC,CAAC,CAAC,CACF,CAAC,gBAAgB,aAAhB,gBAAgB,cAAhB,gBAAgB,GAAI,CACnB,KAAC,KAAK,IACJ,OAAO,EAAC,mCAAmC,EAC3C,QAAQ,EAAC,SAAS,GAClB,CACH,CAAC,CACH,GACM,IACL,EAEL,QAAQ,CAAC,CAAC,CAAC,CACV,YACE,SAAS,EACP,SAAS;gCACP,CAAC,CAAC,6CAA6C;gCAC/C,CAAC,CAAC,2CAA2C,YAGhD,QAAQ,GACP,CACL,CAAC,CAAC,CAAC,IAAI,IACJ,IACD,IACF,CACR,CAAC;IAEF,OAAO,CACL,KAAC,oBAAoB,IACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,YAEb,WAAW,GACS,CACxB,CAAC;AACJ,CAAC","sourcesContent":["'use client';\n\nimport { ReactNode, useTransition } from 'react';\n\nimport { Button } from '@ekanos/ui/button';\nimport { Form } from '@ekanos/ui/form';\nimport { Icon } from '@ekanos/ui/icon';\nimport { 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"]}
|
|
@@ -12,7 +12,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
12
12
|
// exports.default`, so Node's CJS-default interop and a bundler's `__esModule`
|
|
13
13
|
// interop land on the same component object. Do not "tidy" the extension away.
|
|
14
14
|
import Link from 'next/link.js';
|
|
15
|
-
import { Badge } from '@ekanos/ui/badge';
|
|
16
15
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@ekanos/ui/card';
|
|
17
16
|
import { Icon } from '@ekanos/ui/icon';
|
|
18
17
|
import { If } from '@ekanos/ui/if';
|
|
@@ -28,15 +27,7 @@ export function BaseMarketplaceTile(props) {
|
|
|
28
27
|
if (target.nextSibling) {
|
|
29
28
|
target.nextSibling.style.display = 'flex';
|
|
30
29
|
}
|
|
31
|
-
} }), _jsx("div", { className: "bg-border ring-border/50 hidden h-full w-full items-center justify-center rounded-lg text-2xl font-semibold ring-1", children: displayBrandName.charAt(0).toUpperCase() })] })) : (_jsx("div", { className: "bg-border ring-border/50 flex h-16 w-16 items-center justify-center rounded-lg border-2 text-2xl font-semibold ring-1", children: displayBrandName.charAt(0).toUpperCase() })) }), _jsx(CardTitle, { className: "line-clamp-2 text-xl leading-tight font-semibold", children: displayTitle }), _jsx(If, { condition: categories.length > 0, children: _jsxs("div", { className: "mt-2 flex flex-wrap justify-center gap-1", children: [categories.slice(0, 2).map((cat) => (_jsx(
|
|
32
|
-
backgroundColor: productColor
|
|
33
|
-
? `${productColor}66`
|
|
34
|
-
: undefined,
|
|
35
|
-
}, children: cat.name }, cat.id))), _jsx(If, { condition: categories.length > 2, children: _jsxs(Badge, { variant: "secondary", className: "px-2 py-0.5 text-xs font-medium", style: {
|
|
36
|
-
backgroundColor: productColor
|
|
37
|
-
? `${productColor}66`
|
|
38
|
-
: undefined,
|
|
39
|
-
}, children: ["+", categories.length - 2] }) })] }) })] }), _jsx(CardContent, { className: "flex-1 pb-8 text-center", children: _jsx(If, { condition: displaySummary, children: _jsx(CardDescription, { className: "text-muted-foreground line-clamp-2 text-base leading-relaxed", children: displaySummary }) }) }), _jsx(If, { condition: displayBrandName !== displayTitle, children: _jsx("p", { className: "text-muted-foreground/70 absolute right-3 bottom-3 text-sm", children: displayBrandName }) }), _jsx(If, { condition: !!ctaLink, children: _jsx("div", { className: "text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors", "aria-hidden": "true", children: _jsx(Icon, { name: "fa-light fa-arrow-up-right-from-square", className: "h-4 w-4" }) }) })] }));
|
|
30
|
+
} }), _jsx("div", { className: "bg-border ring-border/50 hidden h-full w-full items-center justify-center rounded-lg text-2xl font-semibold ring-1", children: displayBrandName.charAt(0).toUpperCase() })] })) : (_jsx("div", { className: "bg-border ring-border/50 flex h-16 w-16 items-center justify-center rounded-lg border-2 text-2xl font-semibold ring-1", children: displayBrandName.charAt(0).toUpperCase() })) }), _jsx(CardTitle, { className: "line-clamp-2 text-xl leading-tight font-semibold", children: displayTitle }), _jsx(If, { condition: categories.length > 0, children: _jsxs("div", { className: "mt-2 flex flex-wrap justify-center gap-1", children: [categories.slice(0, 2).map((cat) => (_jsx(CategoryChip, { productColor: productColor, children: cat.name }, cat.id))), _jsx(If, { condition: categories.length > 2, children: _jsxs(CategoryChip, { productColor: productColor, children: ["+", categories.length - 2] }) })] }) })] }), _jsx(CardContent, { className: "flex-1 pb-8 text-center", children: _jsx(If, { condition: displaySummary, children: _jsx(CardDescription, { className: "text-muted-foreground line-clamp-2 text-base leading-relaxed", children: displaySummary }) }) }), _jsx(If, { condition: displayBrandName !== displayTitle, children: _jsx("p", { className: "text-muted-foreground/70 absolute right-3 bottom-3 text-sm", children: displayBrandName }) }), _jsx(If, { condition: !!ctaLink, children: _jsx("div", { className: "text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors", "aria-hidden": "true", children: _jsx(Icon, { name: "fa-light fa-arrow-up-right-from-square", className: "h-4 w-4" }) }) })] }));
|
|
40
31
|
// External link: opens in new tab
|
|
41
32
|
if (ctaLink) {
|
|
42
33
|
return (_jsx("a", { href: ctaLink, target: "_blank", rel: "noopener noreferrer", className: "block h-full", "aria-label": `${displayTitle} (opens in new tab)`, children: cardContent }));
|
|
@@ -48,4 +39,15 @@ export function BaseMarketplaceTile(props) {
|
|
|
48
39
|
// Fallback: render without Link (for backwards compatibility)
|
|
49
40
|
return cardContent;
|
|
50
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* A category chip, tinted with the product's own colour when it has one. The
|
|
44
|
+
* colour is a partner's product colour from the product schema, not a theme
|
|
45
|
+
* token — a data colour, so it is a chip rather than a `Badge` (a Badge owns
|
|
46
|
+
* its colour). The tint is that colour at 40% alpha.
|
|
47
|
+
*/
|
|
48
|
+
function CategoryChip({ productColor, children, }) {
|
|
49
|
+
return (_jsx("span", { className: cn('text-secondary-foreground inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap', productColor ? 'bg-(--product-color)/40' : 'bg-secondary'), style: productColor
|
|
50
|
+
? { '--product-color': productColor }
|
|
51
|
+
: undefined, children: children }));
|
|
52
|
+
}
|
|
51
53
|
//# sourceMappingURL=base-marketplace-tile.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-marketplace-tile.js","sourceRoot":"","sources":["../../src/components/base-marketplace-tile.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\n// `next/link.js`, not `next/link`. `next` ships no `exports` map, so the\n// extensionless subpath only resolves under a bundler — Node's own ESM loader\n// throws ERR_MODULE_NOT_FOUND, and any partner running vitest or plain Node\n// against the published tarball loses this whole module. The `.js` form is not\n// a bypass of Next's resolution: `create-compiler-aliases.js` keys its aliases\n// on the RESOLVED FILE PATH (`<next>/link.js`, with the comment \"Handle fully\n// specified imports like `next/image.js`\"), so an App Router build still gets\n// `next/dist/client/app-dir/link`, exactly as `next/link` would. Next's dist\n// also ends with `Object.assign(exports.default, exports); module.exports =\n// exports.default`, so Node's CJS-default interop and a bundler's `__esModule`\n// interop land on the same component object. Do not \"tidy\" the extension away.\nimport Link from 'next/link.js';\n\nimport { Badge } from '@ekanos/ui/badge';\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { MarketplaceTileProps } from '../types/integration';\n\n// Type for product category\nexport interface ProductCategory {\n id: string;\n name: string;\n slug: string;\n description?: string;\n}\n\nexport type BaseMarketplaceTileProps = MarketplaceTileProps & {\n /** The display name of the product */\n name: string;\n /** Full description of the product (used for detail pages) */\n description: string;\n /** URL-friendly identifier for the product */\n slug: string;\n /** Short summary text displayed on the tile (1-2 sentences) */\n summary?: string;\n /** Name of the company/brand that provides this product */\n brandName?: string;\n /** Path to the product logo image */\n logoPath?: string;\n /** Text for the call-to-action button */\n ctaText?: string;\n /** External URL for the CTA (opens in new tab when set) */\n ctaLink?: string;\n\n /** Array of categories this product belongs to */\n categories?: ProductCategory[];\n\n /** Hex color from product schema for tile theming */\n productColor?: string;\n /** Additional CSS classes to apply to the tile */\n className?: string;\n\n /** Internal navigation URL for the integration detail page */\n href?: string;\n};\n\nexport function BaseMarketplaceTile(props: BaseMarketplaceTileProps) {\n const {\n name,\n summary,\n brandName,\n logoPath,\n categories = [],\n productColor,\n className,\n href,\n ctaLink,\n } = props;\n\n const displayTitle = name || 'Unknown Product';\n const displaySummary = summary;\n const displayBrandName = brandName || displayTitle;\n\n const cardContent = (\n <Card\n className={cn(\n 'group bg-card relative flex h-full w-full cursor-pointer flex-col overflow-hidden border-0 pt-3 pb-2 shadow-sm transition-all duration-300 hover:shadow-md',\n className,\n )}\n >\n {/* Header: Centered Logo, Title, Categories */}\n <CardHeader className=\"flex flex-col items-center pt-6 pb-3 text-center\">\n {/* Logo */}\n <div className=\"mb-3\">\n {logoPath ? (\n <div className=\"flex h-16 w-16 items-center justify-center\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoPath}\n alt={`${displayBrandName} logo`}\n width={64}\n height={64}\n className=\"max-h-full max-w-full object-contain\"\n onError={(e) => {\n const target = e.target as HTMLImageElement;\n target.style.display = 'none';\n if (target.nextSibling) {\n (target.nextSibling as HTMLElement).style.display = 'flex';\n }\n }}\n />\n <div className=\"bg-border ring-border/50 hidden h-full w-full items-center justify-center rounded-lg text-2xl font-semibold ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n </div>\n ) : (\n <div className=\"bg-border ring-border/50 flex h-16 w-16 items-center justify-center rounded-lg border-2 text-2xl font-semibold ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n )}\n </div>\n\n {/* Title */}\n <CardTitle className=\"line-clamp-2 text-xl leading-tight font-semibold\">\n {displayTitle}\n </CardTitle>\n\n {/* Category Badges - Under Title */}\n <If condition={categories.length > 0}>\n <div className=\"mt-2 flex flex-wrap justify-center gap-1\">\n {categories.slice(0, 2).map((cat) => (\n <Badge\n key={cat.id}\n variant=\"secondary\"\n className=\"px-2 py-0.5 text-xs font-medium\"\n style={{\n backgroundColor: productColor\n ? `${productColor}66`\n : undefined,\n }}\n >\n {cat.name}\n </Badge>\n ))}\n <If condition={categories.length > 2}>\n <Badge\n variant=\"secondary\"\n className=\"px-2 py-0.5 text-xs font-medium\"\n style={{\n backgroundColor: productColor\n ? `${productColor}66`\n : undefined,\n }}\n >\n +{categories.length - 2}\n </Badge>\n </If>\n </div>\n </If>\n </CardHeader>\n\n {/* Content: Summary */}\n <CardContent className=\"flex-1 pb-8 text-center\">\n <If condition={displaySummary}>\n <CardDescription className=\"text-muted-foreground line-clamp-2 text-base leading-relaxed\">\n {displaySummary}\n </CardDescription>\n </If>\n </CardContent>\n\n {/* Brand Name - Bottom Right */}\n <If condition={displayBrandName !== displayTitle}>\n <p className=\"text-muted-foreground/70 absolute right-3 bottom-3 text-sm\">\n {displayBrandName}\n </p>\n </If>\n\n {/* External Link Indicator */}\n <If condition={!!ctaLink}>\n <div\n className=\"text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors\"\n aria-hidden=\"true\"\n >\n <Icon\n name=\"fa-light fa-arrow-up-right-from-square\"\n className=\"h-4 w-4\"\n />\n </div>\n </If>\n </Card>\n );\n\n // External link: opens in new tab\n if (ctaLink) {\n return (\n <a\n href={ctaLink}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block h-full\"\n aria-label={`${displayTitle} (opens in new tab)`}\n >\n {cardContent}\n </a>\n );\n }\n\n // Internal link: uses Next.js Link\n if (href) {\n return (\n <Link href={href} className=\"block h-full\">\n {cardContent}\n </Link>\n );\n }\n\n // Fallback: render without Link (for backwards compatibility)\n return cardContent;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"base-marketplace-tile.js","sourceRoot":"","sources":["../../src/components/base-marketplace-tile.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport type { CSSProperties, ReactNode } from 'react';\n\n// `next/link.js`, not `next/link`. `next` ships no `exports` map, so the\n// extensionless subpath only resolves under a bundler — Node's own ESM loader\n// throws ERR_MODULE_NOT_FOUND, and any partner running vitest or plain Node\n// against the published tarball loses this whole module. The `.js` form is not\n// a bypass of Next's resolution: `create-compiler-aliases.js` keys its aliases\n// on the RESOLVED FILE PATH (`<next>/link.js`, with the comment \"Handle fully\n// specified imports like `next/image.js`\"), so an App Router build still gets\n// `next/dist/client/app-dir/link`, exactly as `next/link` would. Next's dist\n// also ends with `Object.assign(exports.default, exports); module.exports =\n// exports.default`, so Node's CJS-default interop and a bundler's `__esModule`\n// interop land on the same component object. Do not \"tidy\" the extension away.\nimport Link from 'next/link.js';\n\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@ekanos/ui/card';\nimport { Icon } from '@ekanos/ui/icon';\nimport { If } from '@ekanos/ui/if';\nimport { cn } from '@ekanos/ui/utils';\n\nimport type { MarketplaceTileProps } from '../types/integration';\n\n// Type for product category\nexport interface ProductCategory {\n id: string;\n name: string;\n slug: string;\n description?: string;\n}\n\nexport type BaseMarketplaceTileProps = MarketplaceTileProps & {\n /** The display name of the product */\n name: string;\n /** Full description of the product (used for detail pages) */\n description: string;\n /** URL-friendly identifier for the product */\n slug: string;\n /** Short summary text displayed on the tile (1-2 sentences) */\n summary?: string;\n /** Name of the company/brand that provides this product */\n brandName?: string;\n /** Path to the product logo image */\n logoPath?: string;\n /** Text for the call-to-action button */\n ctaText?: string;\n /** External URL for the CTA (opens in new tab when set) */\n ctaLink?: string;\n\n /** Array of categories this product belongs to */\n categories?: ProductCategory[];\n\n /** Hex color from product schema for tile theming */\n productColor?: string;\n /** Additional CSS classes to apply to the tile */\n className?: string;\n\n /** Internal navigation URL for the integration detail page */\n href?: string;\n};\n\nexport function BaseMarketplaceTile(props: BaseMarketplaceTileProps) {\n const {\n name,\n summary,\n brandName,\n logoPath,\n categories = [],\n productColor,\n className,\n href,\n ctaLink,\n } = props;\n\n const displayTitle = name || 'Unknown Product';\n const displaySummary = summary;\n const displayBrandName = brandName || displayTitle;\n\n const cardContent = (\n <Card\n className={cn(\n 'group bg-card relative flex h-full w-full cursor-pointer flex-col overflow-hidden border-0 pt-3 pb-2 shadow-sm transition-all duration-300 hover:shadow-md',\n className,\n )}\n >\n {/* Header: Centered Logo, Title, Categories */}\n <CardHeader className=\"flex flex-col items-center pt-6 pb-3 text-center\">\n {/* Logo */}\n <div className=\"mb-3\">\n {logoPath ? (\n <div className=\"flex h-16 w-16 items-center justify-center\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={logoPath}\n alt={`${displayBrandName} logo`}\n width={64}\n height={64}\n className=\"max-h-full max-w-full object-contain\"\n onError={(e) => {\n const target = e.target as HTMLImageElement;\n target.style.display = 'none';\n if (target.nextSibling) {\n (target.nextSibling as HTMLElement).style.display = 'flex';\n }\n }}\n />\n <div className=\"bg-border ring-border/50 hidden h-full w-full items-center justify-center rounded-lg text-2xl font-semibold ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n </div>\n ) : (\n <div className=\"bg-border ring-border/50 flex h-16 w-16 items-center justify-center rounded-lg border-2 text-2xl font-semibold ring-1\">\n {displayBrandName.charAt(0).toUpperCase()}\n </div>\n )}\n </div>\n\n {/* Title */}\n <CardTitle className=\"line-clamp-2 text-xl leading-tight font-semibold\">\n {displayTitle}\n </CardTitle>\n\n {/* Category Badges - Under Title */}\n <If condition={categories.length > 0}>\n <div className=\"mt-2 flex flex-wrap justify-center gap-1\">\n {categories.slice(0, 2).map((cat) => (\n <CategoryChip key={cat.id} productColor={productColor}>\n {cat.name}\n </CategoryChip>\n ))}\n <If condition={categories.length > 2}>\n <CategoryChip productColor={productColor}>\n +{categories.length - 2}\n </CategoryChip>\n </If>\n </div>\n </If>\n </CardHeader>\n\n {/* Content: Summary */}\n <CardContent className=\"flex-1 pb-8 text-center\">\n <If condition={displaySummary}>\n <CardDescription className=\"text-muted-foreground line-clamp-2 text-base leading-relaxed\">\n {displaySummary}\n </CardDescription>\n </If>\n </CardContent>\n\n {/* Brand Name - Bottom Right */}\n <If condition={displayBrandName !== displayTitle}>\n <p className=\"text-muted-foreground/70 absolute right-3 bottom-3 text-sm\">\n {displayBrandName}\n </p>\n </If>\n\n {/* External Link Indicator */}\n <If condition={!!ctaLink}>\n <div\n className=\"text-muted-foreground/60 group-hover:text-foreground absolute bottom-3 left-3 transition-colors\"\n aria-hidden=\"true\"\n >\n <Icon\n name=\"fa-light fa-arrow-up-right-from-square\"\n className=\"h-4 w-4\"\n />\n </div>\n </If>\n </Card>\n );\n\n // External link: opens in new tab\n if (ctaLink) {\n return (\n <a\n href={ctaLink}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"block h-full\"\n aria-label={`${displayTitle} (opens in new tab)`}\n >\n {cardContent}\n </a>\n );\n }\n\n // Internal link: uses Next.js Link\n if (href) {\n return (\n <Link href={href} className=\"block h-full\">\n {cardContent}\n </Link>\n );\n }\n\n // Fallback: render without Link (for backwards compatibility)\n return cardContent;\n}\n\n/**\n * A category chip, tinted with the product's own colour when it has one. The\n * colour is a partner's product colour from the product schema, not a theme\n * token — a data colour, so it is a chip rather than a `Badge` (a Badge owns\n * its colour). The tint is that colour at 40% alpha.\n */\nfunction CategoryChip({\n productColor,\n children,\n}: {\n productColor?: string;\n children: ReactNode;\n}) {\n return (\n <span\n className={cn(\n 'text-secondary-foreground inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap',\n productColor ? 'bg-(--product-color)/40' : 'bg-secondary',\n )}\n style={\n productColor\n ? ({ '--product-color': productColor } as CSSProperties)\n : undefined\n }\n >\n {children}\n </span>\n );\n}\n"]}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.
|
|
27
27
|
*/
|
|
28
28
|
export { Widget } from './widgets/widget.js';
|
|
29
|
-
export { WidgetContext, type WidgetActions, type WidgetContextValue, type WidgetHealth, type WidgetMeta, type WidgetRenderState, type WidgetState, } from './widgets/widget-context.js';
|
|
29
|
+
export { WidgetContext, type WidgetActions, type WidgetContextValue, type WidgetData, type WidgetHealth, type WidgetMeta, type WidgetRenderState, type WidgetState, } from './widgets/widget-context.js';
|
|
30
30
|
export { WidgetPreviewProvider } from './widgets/widget-preview-provider.js';
|
|
31
31
|
export type { WidgetPreviewProviderProps } from './widgets/widget-preview-provider.js';
|
|
32
32
|
export { AiPromptChip } from './widgets/ai-prompt-chip.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EACL,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EACL,aAAa,GAQd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAEtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAG9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC","sourcesContent":["/**\n * @ekanos/sdk/components — client-side building blocks.\n *\n * Import from client components only. `Widget` is the compound API every\n * dashboard widget renders through; the activation forms are the two\n * sanctioned activation UX bases (credential-style and OAuth-style), and\n * `BaseMarketplaceTile` is the matching base for the marketplace tile.\n *\n * The SDK OWNS these implementations — the pre-inversion re-exports through\n * `@kit/integrations-core/components/*` are gone; core now re-exports FROM\n * here for first-party code (one source of truth, two consumption paths —\n * one source of truth with two consumption paths. UI primitives come from\n * `@ekanos/ui`\n * (the published slice of the host design system), never `@kit/ui`.\n *\n * Server seam: nothing here imports a server module. `OAuthActivationForm`\n * performs activation through the host-injected actions seam\n * (`IntegrationActivationProvider`, @ekanos/sdk/hooks) — a server-action\n * REFERENCE bound by the host, not server code shipped to the browser.\n *\n * `WidgetContext` is the single context identity both sides share: the\n * host's `DashboardWidgetProvider` (host chrome — persistence callbacks,\n * config hierarchy) provides it, the SDK's `Widget.*` reads it.\n *\n * Surface discipline: additions require an entry in\n * api-report.md, regenerated by `pnpm --filter @ekanos/sdk api-report`.\n */\n\nexport { Widget } from './widgets/widget';\nexport {\n WidgetContext,\n type WidgetActions,\n type WidgetContextValue,\n type WidgetData,\n type WidgetHealth,\n type WidgetMeta,\n type WidgetRenderState,\n type WidgetState,\n} from './widgets/widget-context';\nexport { WidgetPreviewProvider } from './widgets/widget-preview-provider';\nexport type { WidgetPreviewProviderProps } from './widgets/widget-preview-provider';\nexport { AiPromptChip } from './widgets/ai-prompt-chip';\nexport { AskIcon } from './widgets/ask-icon';\nexport { WidgetAskBar } from './widgets/widget-ask-bar';\nexport { WidgetDataLoading } from './widgets/widget-state-components';\n\nexport { BaseActivationForm } from './base-activation-form';\nexport type { BaseActivationFormProps } from './base-activation-form';\nexport {\n ActivationDialog,\n ActivationInline,\n BaseActivationDialog,\n} from './base-activation-dialog';\n\nexport { BaseMarketplaceTile } from './base-marketplace-tile';\nexport type { BaseMarketplaceTileProps } from './base-marketplace-tile';\n\nexport { OAuthActivationForm } from './oauth-activation-form';\nexport type { OAuthActivationFormConfig } from './oauth-activation-form';\n\nexport { currentReturnPath } from './current-return-path';\n"]}
|
|
@@ -4,21 +4,24 @@ import type { ActivationFormProps } from '../types/integration.js';
|
|
|
4
4
|
export interface OAuthActivationFormConfig {
|
|
5
5
|
/** Integration slug — used for activation, the connect route, and ids. */
|
|
6
6
|
slug: string;
|
|
7
|
-
/** Human name used in default button
|
|
7
|
+
/** Human name used in default button copy, e.g. 'QuickBooks'. */
|
|
8
8
|
displayName: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
/** Shown only when the form renders its own dialog (never inline). */
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Shown only when the form renders its own dialog (never inline). */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** @deprecated The host owns the success moment; this is not shown inline. */
|
|
14
|
+
successMessage?: string;
|
|
15
|
+
/** @deprecated The host owns the success moment; this is not shown inline. */
|
|
13
16
|
successTitle?: string;
|
|
14
17
|
/** Defaults to `/api/integrations/<slug>/connect`. */
|
|
15
18
|
connectPath?: string;
|
|
16
|
-
/**
|
|
19
|
+
/** @deprecated No banner renders on return; the host toasts instead. */
|
|
17
20
|
connectedBanner?: string;
|
|
18
|
-
/** Defaults to `
|
|
19
|
-
connectButtonText?:
|
|
20
|
-
/** Defaults to `
|
|
21
|
-
activateButtonText?:
|
|
21
|
+
/** Defaults to `Continue with <displayName>`. */
|
|
22
|
+
connectButtonText?: ReactNode;
|
|
23
|
+
/** Defaults to `Connect`. Only reachable with `autoActivateOnConnect: false`. */
|
|
24
|
+
activateButtonText?: ReactNode;
|
|
22
25
|
/** externalId is `<prefix>_<timestamp>`; prefix defaults to the slug. */
|
|
23
26
|
externalIdPrefix?: string;
|
|
24
27
|
/**
|
|
@@ -26,8 +29,8 @@ export interface OAuthActivationFormConfig {
|
|
|
26
29
|
*
|
|
27
30
|
* Set `false` for integrations that expose post-connect configuration — the
|
|
28
31
|
* user needs a chance to review or change it before activating, which
|
|
29
|
-
* auto-activation skips right past. Connecting then leaves the form on
|
|
30
|
-
* "
|
|
32
|
+
* auto-activation skips right past. Connecting then leaves the form on a
|
|
33
|
+
* "Connect" button as a deliberate second step.
|
|
31
34
|
*/
|
|
32
35
|
autoActivateOnConnect?: boolean;
|
|
33
36
|
}
|
|
@@ -35,13 +38,17 @@ type OAuthActivationFormProps = ActivationFormProps & {
|
|
|
35
38
|
config: OAuthActivationFormConfig;
|
|
36
39
|
checkConnection: ConnectionCheckAction;
|
|
37
40
|
errorParam?: OAuthErrorParam;
|
|
38
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Fields to fill in AFTER signing in, for an integration with
|
|
43
|
+
* `autoActivateOnConnect: false`. Rendered only once connected. Not a
|
|
44
|
+
* place for feature lists or permission badges — the host shows those.
|
|
45
|
+
*/
|
|
39
46
|
children?: ReactNode;
|
|
40
47
|
};
|
|
41
48
|
/**
|
|
42
|
-
* Shared activation form for OAuth-backed integrations:
|
|
43
|
-
*
|
|
44
|
-
* auto-activates once the
|
|
49
|
+
* Shared activation form for OAuth-backed integrations: one "Continue with
|
|
50
|
+
* X" button that sends the browser to the integration's connect route, then
|
|
51
|
+
* auto-activates once the redirect returns with a live connection.
|
|
45
52
|
*
|
|
46
53
|
* Integrations with real activation fields (API keys, domains, …) should
|
|
47
54
|
* keep a bespoke form built on BaseActivationForm instead.
|
|
@@ -2,25 +2,26 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
4
4
|
import { Icon } from '@ekanos/ui/icon';
|
|
5
|
+
import { Trans } from '@ekanos/ui/trans';
|
|
5
6
|
import { useForm } from 'react-hook-form';
|
|
6
7
|
import { useActivateIntegration } from '../hooks/use-activate-integration.js';
|
|
7
8
|
import { useOAuthConnectionStatus, } from '../hooks/use-oauth-connection-status.js';
|
|
8
9
|
import { BaseActivationForm } from './base-activation-form.js';
|
|
9
10
|
import { currentReturnPath } from './current-return-path.js';
|
|
10
11
|
/**
|
|
11
|
-
* Shared activation form for OAuth-backed integrations:
|
|
12
|
-
*
|
|
13
|
-
* auto-activates once the
|
|
12
|
+
* Shared activation form for OAuth-backed integrations: one "Continue with
|
|
13
|
+
* X" button that sends the browser to the integration's connect route, then
|
|
14
|
+
* auto-activates once the redirect returns with a live connection.
|
|
14
15
|
*
|
|
15
16
|
* Integrations with real activation fields (API keys, domains, …) should
|
|
16
17
|
* keep a bespoke form built on BaseActivationForm instead.
|
|
17
18
|
*/
|
|
18
19
|
export function OAuthActivationForm(props) {
|
|
19
20
|
var _a;
|
|
20
|
-
const { config, checkConnection, errorParam, children, accountId, sourceId, teamAccountId, onSuccess, onCancel, isLoading: externalLoading = false, inline = false, } = props;
|
|
21
|
-
const { slug, displayName, title
|
|
21
|
+
const { config, checkConnection, errorParam, children, accountId, sourceId, teamAccountId, onSuccess, onCancel, isLoading: externalLoading = false, inline = false, reconnect = false, } = props;
|
|
22
|
+
const { slug, displayName, title = `Connect ${displayName}`, description = '', connectPath = `/api/integrations/${slug}/connect`, connectButtonText, activateButtonText, externalIdPrefix = slug, autoActivateOnConnect = true, } = config;
|
|
22
23
|
const [error, setError] = useState(null);
|
|
23
|
-
const [
|
|
24
|
+
const [activated, setActivated] = useState(false);
|
|
24
25
|
const { data: connectionStatus, oauthJustCompleted, oauthError, isLoading: isCheckingConnection, } = useOAuthConnectionStatus(accountId, {
|
|
25
26
|
connectionKey: `${slug}-connection`,
|
|
26
27
|
checkConnection,
|
|
@@ -46,6 +47,8 @@ export function OAuthActivationForm(props) {
|
|
|
46
47
|
}
|
|
47
48
|
window.location.href = `${connectPath}?${params.toString()}`;
|
|
48
49
|
}, [accountId, teamAccountId, connectPath]);
|
|
50
|
+
// Reports success exactly once, straight to the host — no in-form panel,
|
|
51
|
+
// no second call from a "Continue" button.
|
|
49
52
|
const handleActivate = useCallback(async () => {
|
|
50
53
|
try {
|
|
51
54
|
setError(null);
|
|
@@ -56,10 +59,9 @@ export function OAuthActivationForm(props) {
|
|
|
56
59
|
integrationSlug: slug,
|
|
57
60
|
activationData: {},
|
|
58
61
|
});
|
|
59
|
-
|
|
62
|
+
setActivated(true);
|
|
60
63
|
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess({
|
|
61
64
|
success: true,
|
|
62
|
-
configData: form.getValues(),
|
|
63
65
|
externalId: `${externalIdPrefix}_${Date.now()}`,
|
|
64
66
|
});
|
|
65
67
|
}
|
|
@@ -72,17 +74,25 @@ export function OAuthActivationForm(props) {
|
|
|
72
74
|
sourceId,
|
|
73
75
|
teamAccountId,
|
|
74
76
|
slug,
|
|
77
|
+
externalIdPrefix,
|
|
75
78
|
activateMutateAsync,
|
|
76
79
|
onSuccess,
|
|
77
|
-
form,
|
|
78
80
|
]);
|
|
79
81
|
const handleSubmit = useCallback(async () => {
|
|
80
|
-
|
|
82
|
+
// A reconnect always goes back through sign-in — that is what issues
|
|
83
|
+
// the fresh token — unless this render IS the return from it.
|
|
84
|
+
if (!isConnected || (reconnect && !oauthJustCompleted)) {
|
|
81
85
|
startConnect();
|
|
82
86
|
return;
|
|
83
87
|
}
|
|
84
88
|
await handleActivate();
|
|
85
|
-
}, [
|
|
89
|
+
}, [
|
|
90
|
+
isConnected,
|
|
91
|
+
reconnect,
|
|
92
|
+
oauthJustCompleted,
|
|
93
|
+
handleActivate,
|
|
94
|
+
startConnect,
|
|
95
|
+
]);
|
|
86
96
|
// Auto-activate after OAuth return, unless the integration opted out so the
|
|
87
97
|
// user can review its post-connect configuration first.
|
|
88
98
|
const hasAutoActivated = useRef(false);
|
|
@@ -90,7 +100,7 @@ export function OAuthActivationForm(props) {
|
|
|
90
100
|
if (autoActivateOnConnect &&
|
|
91
101
|
oauthJustCompleted &&
|
|
92
102
|
isConnected &&
|
|
93
|
-
!
|
|
103
|
+
!activated &&
|
|
94
104
|
!activateIntegration.isPending &&
|
|
95
105
|
!hasAutoActivated.current) {
|
|
96
106
|
hasAutoActivated.current = true;
|
|
@@ -103,17 +113,21 @@ export function OAuthActivationForm(props) {
|
|
|
103
113
|
autoActivateOnConnect,
|
|
104
114
|
oauthJustCompleted,
|
|
105
115
|
isConnected,
|
|
106
|
-
|
|
116
|
+
activated,
|
|
107
117
|
activateIntegration.isPending,
|
|
108
118
|
handleActivate,
|
|
109
119
|
]);
|
|
110
120
|
const displayError = error || oauthError;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
// The round-trip is in flight from the moment the redirect lands until
|
|
122
|
+
// activation reports back: the button reads as busy the whole way rather
|
|
123
|
+
// than flashing "Continue with X" a second time.
|
|
124
|
+
const finishing = autoActivateOnConnect && oauthJustCompleted && isConnected && !displayError;
|
|
125
|
+
const signInAgain = reconnect && !oauthJustCompleted;
|
|
126
|
+
const buttonText = signInAgain
|
|
127
|
+
? (connectButtonText !== null && connectButtonText !== void 0 ? connectButtonText : (_jsx(Trans, { i18nKey: "common:integrationConnect.reconnectWith", values: { name: displayName }, defaults: `Reconnect with ${displayName}` })))
|
|
128
|
+
: isConnected
|
|
129
|
+
? (activateButtonText !== null && activateButtonText !== void 0 ? activateButtonText : (_jsx(Trans, { i18nKey: "common:integrationConnect.connect", defaults: "Connect" })))
|
|
130
|
+
: (connectButtonText !== null && connectButtonText !== void 0 ? connectButtonText : (_jsx(Trans, { i18nKey: "common:integrationConnect.continueWith", values: { name: displayName }, defaults: `Continue with ${displayName}` })));
|
|
131
|
+
return (_jsx(BaseActivationForm, { isOpen: true, inline: inline, onClose: () => onCancel === null || onCancel === void 0 ? void 0 : onCancel(), title: title, description: description, form: form, onSubmit: handleSubmit, isLoading: externalLoading || activateIntegration.isPending || finishing, error: displayError, submitButtonText: buttonText, submitButtonLoadingText: isConnected && !signInAgain ? (_jsx(Trans, { i18nKey: "common:integrationConnect.finishing", defaults: "Finishing setup\u2026" })) : (_jsx(Trans, { i18nKey: "common:integrationConnect.redirecting", defaults: "Opening sign-in\u2026" })), submitButtonDisabled: isCheckingConnection, footnote: signInAgain ? (_jsx(Trans, { i18nKey: "common:integrationConnect.reconnectFootnote", values: { name: displayName }, defaults: `You'll sign in on ${displayName}'s site again and come straight back here.` })) : isConnected ? (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [_jsx(Icon, { name: "fa-light fa-circle-check", className: "text-success h-4 w-4" }), _jsx(Trans, { i18nKey: "common:integrationConnect.signedIn", values: { name: displayName }, defaults: `Signed in to ${displayName}.` })] })) : (_jsx(Trans, { i18nKey: "common:integrationConnect.oauthFootnote", values: { name: displayName }, defaults: `You'll sign in on ${displayName}'s site and come straight back here.` })), children: isConnected && children ? children : null }));
|
|
118
132
|
}
|
|
119
133
|
//# sourceMappingURL=oauth-activation-form.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-activation-form.js","sourceRoot":"","sources":["../../src/components/oauth-activation-form.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { Icon } from '@ekanos/ui/icon';\nimport { useForm } from 'react-hook-form';\n\nimport { useActivateIntegration } from '../hooks/use-activate-integration';\nimport {\n type ConnectionCheckAction,\n type OAuthErrorParam,\n useOAuthConnectionStatus,\n} from '../hooks/use-oauth-connection-status';\nimport type { ActivationFormProps } from '../types/integration';\nimport { BaseActivationForm } from './base-activation-form';\nimport { currentReturnPath } from './current-return-path';\n\nexport interface OAuthActivationFormConfig {\n /** Integration slug — used for activation, the connect route, and ids. */\n slug: string;\n /** Human name used in default button/banner copy, e.g. 'QuickBooks'. */\n displayName: string;\n title: string;\n description: string;\n successMessage: string;\n /** Defaults to \"You're all set!\" */\n successTitle?: string;\n /** Defaults to `/api/integrations/<slug>/connect`. */\n connectPath?: string;\n /** Defaults to `<displayName> account connected`. */\n connectedBanner?: string;\n /** Defaults to `Connect with <displayName>`. */\n connectButtonText?: string;\n /** Defaults to `Activate <displayName>`. */\n activateButtonText?: string;\n /** externalId is `<prefix>_<timestamp>`; prefix defaults to the slug. */\n externalIdPrefix?: string;\n /**\n * Whether returning from OAuth should immediately activate (default `true`).\n *\n * Set `false` for integrations that expose post-connect configuration — the\n * user needs a chance to review or change it before activating, which\n * auto-activation skips right past. Connecting then leaves the form on an\n * \"Activate <X>\" button as a deliberate second step.\n */\n autoActivateOnConnect?: boolean;\n}\n\ntype OAuthActivationFormProps = ActivationFormProps & {\n config: OAuthActivationFormConfig;\n checkConnection: ConnectionCheckAction;\n errorParam?: OAuthErrorParam;\n /** Extra content (feature showcases, permission badges) rendered above the connected banner. */\n children?: ReactNode;\n};\n\n/**\n * Shared activation form for OAuth-backed integrations: shows a \"Connect\n * with X\" button that redirects to the integration's connect route, then\n * auto-activates once the OAuth redirect returns with a live connection.\n *\n * Integrations with real activation fields (API keys, domains, …) should\n * keep a bespoke form built on BaseActivationForm instead.\n */\nexport function OAuthActivationForm(props: OAuthActivationFormProps) {\n const {\n config,\n checkConnection,\n errorParam,\n children,\n accountId,\n sourceId,\n teamAccountId,\n onSuccess,\n onCancel,\n isLoading: externalLoading = false,\n inline = false,\n } = props;\n\n const {\n slug,\n displayName,\n title,\n description,\n successMessage,\n successTitle = \"You're all set!\",\n connectPath = `/api/integrations/${slug}/connect`,\n connectedBanner = `${displayName} account connected`,\n connectButtonText = `Connect with ${displayName}`,\n activateButtonText = `Activate ${displayName}`,\n externalIdPrefix = slug,\n autoActivateOnConnect = true,\n } = config;\n\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n const {\n data: connectionStatus,\n oauthJustCompleted,\n oauthError,\n isLoading: isCheckingConnection,\n } = useOAuthConnectionStatus(accountId, {\n connectionKey: `${slug}-connection`,\n checkConnection,\n errorParam,\n });\n\n const isConnected = connectionStatus?.isConnected ?? false;\n\n const form = useForm({ defaultValues: {} });\n\n const activateIntegration = useActivateIntegration();\n const { mutateAsync: activateMutateAsync } = activateIntegration;\n\n const startConnect = useCallback(() => {\n setError(null);\n const params = new URLSearchParams({\n accountId,\n returnPath: currentReturnPath(),\n });\n\n // For a user-scoped integration `accountId` is the PERSONAL account (see\n // the connect modal's scope-resolved id), which has no\n // source_id — so an OAuth callback cannot materialize workspaces from it\n // alone. Carry the team through as well; callbacks that don't read it\n // simply ignore the extra param.\n if (teamAccountId) {\n params.set('teamAccountId', teamAccountId);\n }\n\n window.location.href = `${connectPath}?${params.toString()}`;\n }, [accountId, teamAccountId, connectPath]);\n\n const handleActivate = useCallback(async () => {\n try {\n setError(null);\n await activateMutateAsync({\n accountId,\n sourceId,\n teamAccountId,\n integrationSlug: slug,\n activationData: {},\n });\n setSuccess(true);\n\n onSuccess?.({\n success: true,\n configData: form.getValues(),\n externalId: `${externalIdPrefix}_${Date.now()}`,\n });\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Activation failed';\n setError(errorMessage);\n }\n }, [\n accountId,\n sourceId,\n teamAccountId,\n slug,\n activateMutateAsync,\n onSuccess,\n form,\n ]);\n\n const handleSubmit = useCallback(async () => {\n if (!isConnected) {\n startConnect();\n return;\n }\n\n await handleActivate();\n }, [isConnected, handleActivate, startConnect]);\n\n // Auto-activate after OAuth return, unless the integration opted out so the\n // user can review its post-connect configuration first.\n const hasAutoActivated = useRef(false);\n\n useEffect(() => {\n if (\n autoActivateOnConnect &&\n oauthJustCompleted &&\n isConnected &&\n !success &&\n !activateIntegration.isPending &&\n !hasAutoActivated.current\n ) {\n hasAutoActivated.current = true;\n\n const id = requestAnimationFrame(() => {\n void handleActivate();\n });\n\n return () => cancelAnimationFrame(id);\n }\n }, [\n autoActivateOnConnect,\n oauthJustCompleted,\n isConnected,\n success,\n activateIntegration.isPending,\n handleActivate,\n ]);\n\n const displayError = error || oauthError;\n const buttonText = isConnected ? activateButtonText : connectButtonText;\n const buttonLoadingText = isConnected ? 'Activating...' : 'Connecting...';\n\n return (\n <BaseActivationForm\n isOpen={true}\n inline={inline}\n onClose={() => onCancel?.()}\n title={title}\n description={description}\n form={form}\n onSubmit={handleSubmit}\n isLoading={\n externalLoading || activateIntegration.isPending || isCheckingConnection\n }\n error={displayError}\n success={success}\n successTitle={successTitle}\n successMessage={successMessage}\n onSuccessClose={() =>\n onSuccess?.({\n success: true,\n configData: form.getValues(),\n externalId: `${externalIdPrefix}_${Date.now()}`,\n })\n }\n submitButtonText={buttonText}\n submitButtonLoadingText={buttonLoadingText}\n >\n {children}\n\n {isConnected && !success && (\n <div className=\"border-success/30 bg-success/10 flex items-center justify-center gap-2 rounded-lg border p-4\">\n <Icon\n name=\"fa-light fa-circle-check\"\n className=\"text-success h-5 w-5\"\n />\n <span className=\"text-success text-sm font-medium\">\n {connectedBanner}\n </span>\n </div>\n )}\n </BaseActivationForm>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"oauth-activation-form.js","sourceRoot":"","sources":["../../src/components/oauth-activation-form.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport {\n type ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { Icon } from '@ekanos/ui/icon';\nimport { Trans } from '@ekanos/ui/trans';\nimport { useForm } from 'react-hook-form';\n\nimport { useActivateIntegration } from '../hooks/use-activate-integration';\nimport {\n type ConnectionCheckAction,\n type OAuthErrorParam,\n useOAuthConnectionStatus,\n} from '../hooks/use-oauth-connection-status';\nimport type { ActivationFormProps } from '../types/integration';\nimport { BaseActivationForm } from './base-activation-form';\nimport { currentReturnPath } from './current-return-path';\n\nexport interface OAuthActivationFormConfig {\n /** Integration slug — used for activation, the connect route, and ids. */\n slug: string;\n /** Human name used in default button copy, e.g. 'QuickBooks'. */\n displayName: string;\n /** Shown only when the form renders its own dialog (never inline). */\n title?: string;\n /** Shown only when the form renders its own dialog (never inline). */\n description?: string;\n /** @deprecated The host owns the success moment; this is not shown inline. */\n successMessage?: string;\n /** @deprecated The host owns the success moment; this is not shown inline. */\n successTitle?: string;\n /** Defaults to `/api/integrations/<slug>/connect`. */\n connectPath?: string;\n /** @deprecated No banner renders on return; the host toasts instead. */\n connectedBanner?: string;\n /** Defaults to `Continue with <displayName>`. */\n connectButtonText?: ReactNode;\n /** Defaults to `Connect`. Only reachable with `autoActivateOnConnect: false`. */\n activateButtonText?: ReactNode;\n /** externalId is `<prefix>_<timestamp>`; prefix defaults to the slug. */\n externalIdPrefix?: string;\n /**\n * Whether returning from OAuth should immediately activate (default `true`).\n *\n * Set `false` for integrations that expose post-connect configuration — the\n * user needs a chance to review or change it before activating, which\n * auto-activation skips right past. Connecting then leaves the form on a\n * \"Connect\" button as a deliberate second step.\n */\n autoActivateOnConnect?: boolean;\n}\n\ntype OAuthActivationFormProps = ActivationFormProps & {\n config: OAuthActivationFormConfig;\n checkConnection: ConnectionCheckAction;\n errorParam?: OAuthErrorParam;\n /**\n * Fields to fill in AFTER signing in, for an integration with\n * `autoActivateOnConnect: false`. Rendered only once connected. Not a\n * place for feature lists or permission badges — the host shows those.\n */\n children?: ReactNode;\n};\n\n/**\n * Shared activation form for OAuth-backed integrations: one \"Continue with\n * X\" button that sends the browser to the integration's connect route, then\n * auto-activates once the redirect returns with a live connection.\n *\n * Integrations with real activation fields (API keys, domains, …) should\n * keep a bespoke form built on BaseActivationForm instead.\n */\nexport function OAuthActivationForm(props: OAuthActivationFormProps) {\n const {\n config,\n checkConnection,\n errorParam,\n children,\n accountId,\n sourceId,\n teamAccountId,\n onSuccess,\n onCancel,\n isLoading: externalLoading = false,\n inline = false,\n reconnect = false,\n } = props;\n\n const {\n slug,\n displayName,\n title = `Connect ${displayName}`,\n description = '',\n connectPath = `/api/integrations/${slug}/connect`,\n connectButtonText,\n activateButtonText,\n externalIdPrefix = slug,\n autoActivateOnConnect = true,\n } = config;\n\n const [error, setError] = useState<string | null>(null);\n const [activated, setActivated] = useState(false);\n\n const {\n data: connectionStatus,\n oauthJustCompleted,\n oauthError,\n isLoading: isCheckingConnection,\n } = useOAuthConnectionStatus(accountId, {\n connectionKey: `${slug}-connection`,\n checkConnection,\n errorParam,\n });\n\n const isConnected = connectionStatus?.isConnected ?? false;\n\n const form = useForm({ defaultValues: {} });\n\n const activateIntegration = useActivateIntegration();\n const { mutateAsync: activateMutateAsync } = activateIntegration;\n\n const startConnect = useCallback(() => {\n setError(null);\n const params = new URLSearchParams({\n accountId,\n returnPath: currentReturnPath(),\n });\n\n // For a user-scoped integration `accountId` is the PERSONAL account (see\n // the connect modal's scope-resolved id), which has no\n // source_id — so an OAuth callback cannot materialize workspaces from it\n // alone. Carry the team through as well; callbacks that don't read it\n // simply ignore the extra param.\n if (teamAccountId) {\n params.set('teamAccountId', teamAccountId);\n }\n\n window.location.href = `${connectPath}?${params.toString()}`;\n }, [accountId, teamAccountId, connectPath]);\n\n // Reports success exactly once, straight to the host — no in-form panel,\n // no second call from a \"Continue\" button.\n const handleActivate = useCallback(async () => {\n try {\n setError(null);\n await activateMutateAsync({\n accountId,\n sourceId,\n teamAccountId,\n integrationSlug: slug,\n activationData: {},\n });\n setActivated(true);\n\n onSuccess?.({\n success: true,\n externalId: `${externalIdPrefix}_${Date.now()}`,\n });\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Activation failed';\n setError(errorMessage);\n }\n }, [\n accountId,\n sourceId,\n teamAccountId,\n slug,\n externalIdPrefix,\n activateMutateAsync,\n onSuccess,\n ]);\n\n const handleSubmit = useCallback(async () => {\n // A reconnect always goes back through sign-in — that is what issues\n // the fresh token — unless this render IS the return from it.\n if (!isConnected || (reconnect && !oauthJustCompleted)) {\n startConnect();\n return;\n }\n\n await handleActivate();\n }, [\n isConnected,\n reconnect,\n oauthJustCompleted,\n handleActivate,\n startConnect,\n ]);\n\n // Auto-activate after OAuth return, unless the integration opted out so the\n // user can review its post-connect configuration first.\n const hasAutoActivated = useRef(false);\n\n useEffect(() => {\n if (\n autoActivateOnConnect &&\n oauthJustCompleted &&\n isConnected &&\n !activated &&\n !activateIntegration.isPending &&\n !hasAutoActivated.current\n ) {\n hasAutoActivated.current = true;\n\n const id = requestAnimationFrame(() => {\n void handleActivate();\n });\n\n return () => cancelAnimationFrame(id);\n }\n }, [\n autoActivateOnConnect,\n oauthJustCompleted,\n isConnected,\n activated,\n activateIntegration.isPending,\n handleActivate,\n ]);\n\n const displayError = error || oauthError;\n\n // The round-trip is in flight from the moment the redirect lands until\n // activation reports back: the button reads as busy the whole way rather\n // than flashing \"Continue with X\" a second time.\n const finishing =\n autoActivateOnConnect && oauthJustCompleted && isConnected && !displayError;\n\n const signInAgain = reconnect && !oauthJustCompleted;\n\n const buttonText = signInAgain\n ? (connectButtonText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.reconnectWith\"\n values={{ name: displayName }}\n defaults={`Reconnect with ${displayName}`}\n />\n ))\n : isConnected\n ? (activateButtonText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.connect\"\n defaults=\"Connect\"\n />\n ))\n : (connectButtonText ?? (\n <Trans\n i18nKey=\"common:integrationConnect.continueWith\"\n values={{ name: displayName }}\n defaults={`Continue with ${displayName}`}\n />\n ));\n\n return (\n <BaseActivationForm\n isOpen={true}\n inline={inline}\n onClose={() => onCancel?.()}\n title={title}\n description={description}\n form={form}\n onSubmit={handleSubmit}\n isLoading={externalLoading || activateIntegration.isPending || finishing}\n error={displayError}\n submitButtonText={buttonText}\n submitButtonLoadingText={\n isConnected && !signInAgain ? (\n <Trans\n i18nKey=\"common:integrationConnect.finishing\"\n defaults=\"Finishing setup…\"\n />\n ) : (\n <Trans\n i18nKey=\"common:integrationConnect.redirecting\"\n defaults=\"Opening sign-in…\"\n />\n )\n }\n submitButtonDisabled={isCheckingConnection}\n footnote={\n signInAgain ? (\n <Trans\n i18nKey=\"common:integrationConnect.reconnectFootnote\"\n values={{ name: displayName }}\n defaults={`You'll sign in on ${displayName}'s site again and come straight back here.`}\n />\n ) : isConnected ? (\n <span className=\"inline-flex items-center gap-1.5\">\n <Icon\n name=\"fa-light fa-circle-check\"\n className=\"text-success h-4 w-4\"\n />\n <Trans\n i18nKey=\"common:integrationConnect.signedIn\"\n values={{ name: displayName }}\n defaults={`Signed in to ${displayName}.`}\n />\n </span>\n ) : (\n <Trans\n i18nKey=\"common:integrationConnect.oauthFootnote\"\n values={{ name: displayName }}\n defaults={`You'll sign in on ${displayName}'s site and come straight back here.`}\n />\n )\n }\n >\n {isConnected && children ? children : null}\n </BaseActivationForm>\n );\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function formatRelativeTime(from: number, now: number, locale?: string): string;
|
|
2
|
+
/**
|
|
3
|
+
* "3 minutes ago" for `timestamp`, re-rendered every 30 seconds by one shared
|
|
4
|
+
* timer. Returns `null` on the server and during hydration, so the text never
|
|
5
|
+
* mismatches: the server cannot know the viewer's clock.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useRelativeTime(timestamp: number | undefined): string | null;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
|
+
const TICK_MS = 30000;
|
|
4
|
+
const listeners = new Set();
|
|
5
|
+
let timer;
|
|
6
|
+
function subscribe(listener) {
|
|
7
|
+
listeners.add(listener);
|
|
8
|
+
timer !== null && timer !== void 0 ? timer : (timer = setInterval(() => {
|
|
9
|
+
for (const notify of listeners)
|
|
10
|
+
notify();
|
|
11
|
+
}, TICK_MS));
|
|
12
|
+
return () => {
|
|
13
|
+
listeners.delete(listener);
|
|
14
|
+
if (listeners.size === 0 && timer) {
|
|
15
|
+
clearInterval(timer);
|
|
16
|
+
timer = undefined;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Bucketed so consecutive reads inside one tick return the same value, which
|
|
21
|
+
// `useSyncExternalStore` requires of a snapshot.
|
|
22
|
+
function getSnapshot() {
|
|
23
|
+
return Math.floor(Date.now() / TICK_MS) * TICK_MS;
|
|
24
|
+
}
|
|
25
|
+
function getServerSnapshot() {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const UNITS = [
|
|
29
|
+
['day', 86400000],
|
|
30
|
+
['hour', 3600000],
|
|
31
|
+
['minute', 60000],
|
|
32
|
+
];
|
|
33
|
+
export function formatRelativeTime(from, now, locale) {
|
|
34
|
+
const format = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
|
35
|
+
const elapsed = from - now;
|
|
36
|
+
for (const [unit, ms] of UNITS) {
|
|
37
|
+
if (Math.abs(elapsed) >= ms) {
|
|
38
|
+
return format.format(Math.round(elapsed / ms), unit);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return format.format(0, 'second');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* "3 minutes ago" for `timestamp`, re-rendered every 30 seconds by one shared
|
|
45
|
+
* timer. Returns `null` on the server and during hydration, so the text never
|
|
46
|
+
* mismatches: the server cannot know the viewer's clock.
|
|
47
|
+
*/
|
|
48
|
+
export function useRelativeTime(timestamp) {
|
|
49
|
+
const now = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
50
|
+
if (timestamp === undefined || now === null)
|
|
51
|
+
return null;
|
|
52
|
+
const locale = document.documentElement.lang || undefined;
|
|
53
|
+
return formatRelativeTime(timestamp, Math.max(now, timestamp), locale);
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=use-relative-time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-relative-time.js","sourceRoot":"","sources":["../../../src/components/widgets/use-relative-time.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE7C,MAAM,OAAO,GAAG,KAAM,CAAC;AAEvB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;AACxC,IAAI,KAAiD,CAAC;AAEtD,SAAS,SAAS,CAAC,QAAoB;IACrC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,KAAK,aAAL,KAAK,cAAL,KAAK,IAAL,KAAK,GAAK,WAAW,CAAC,GAAG,EAAE;QACzB,KAAK,MAAM,MAAM,IAAI,SAAS;YAAE,MAAM,EAAE,CAAC;IAC3C,CAAC,EAAE,OAAO,CAAC,EAAC;IAEZ,OAAO,GAAG,EAAE;QACV,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3B,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;YAClC,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,KAAK,GAAG,SAAS,CAAC;QACpB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,iDAAiD;AACjD,SAAS,WAAW;IAClB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC;AACpD,CAAC;AAED,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,KAAK,GAA4C;IACrD,CAAC,KAAK,EAAE,QAAU,CAAC;IACnB,CAAC,MAAM,EAAE,OAAS,CAAC;IACnB,CAAC,QAAQ,EAAE,KAAM,CAAC;CACnB,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,GAAW,EACX,MAAe;IAEf,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,IAAI,GAAG,GAAG,CAAC;IAE3B,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5B,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,SAA6B;IAC3D,MAAM,GAAG,GAAG,oBAAoB,CAAC,SAAS,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC;IAE5E,IAAI,SAAS,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEzD,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,CAAC,IAAI,IAAI,SAAS,CAAC;IAC1D,OAAO,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;AACzE,CAAC","sourcesContent":["'use client';\n\nimport { useSyncExternalStore } from 'react';\n\nconst TICK_MS = 30_000;\n\nconst listeners = new Set<() => void>();\nlet timer: ReturnType<typeof setInterval> | undefined;\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n timer ??= setInterval(() => {\n for (const notify of listeners) notify();\n }, TICK_MS);\n\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0 && timer) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n}\n\n// Bucketed so consecutive reads inside one tick return the same value, which\n// `useSyncExternalStore` requires of a snapshot.\nfunction getSnapshot() {\n return Math.floor(Date.now() / TICK_MS) * TICK_MS;\n}\n\nfunction getServerSnapshot() {\n return null;\n}\n\nconst UNITS: [Intl.RelativeTimeFormatUnit, number][] = [\n ['day', 86_400_000],\n ['hour', 3_600_000],\n ['minute', 60_000],\n];\n\nexport function formatRelativeTime(\n from: number,\n now: number,\n locale?: string,\n): string {\n const format = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });\n const elapsed = from - now;\n\n for (const [unit, ms] of UNITS) {\n if (Math.abs(elapsed) >= ms) {\n return format.format(Math.round(elapsed / ms), unit);\n }\n }\n\n return format.format(0, 'second');\n}\n\n/**\n * \"3 minutes ago\" for `timestamp`, re-rendered every 30 seconds by one shared\n * timer. Returns `null` on the server and during hydration, so the text never\n * mismatches: the server cannot know the viewer's clock.\n */\nexport function useRelativeTime(timestamp: number | undefined): string | null {\n const now = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n if (timestamp === undefined || now === null) return null;\n\n const locale = document.documentElement.lang || undefined;\n return formatRelativeTime(timestamp, Math.max(now, timestamp), locale);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget-ask-bar.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-ask-bar.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjE,OAAO,EACL,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEpE,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAMrC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,OAAO,EAAS;;IAC7C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,YAAY,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,WAAW,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,OAAyB,EAAE,EAAE;QAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,OAAO,CAAC,eAAe,CAAC;QAC9D,YAAY,iCAAM,OAAO,KAAE,eAAe,EAAE,MAAM,IAAG,CAAC;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CACjC,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,MAAc,EAAE,EAAE;QACjB,YAAY,iCAAM,OAAO,KAAE,eAAe,EAAE,MAAM,IAAG,CAAC;QACtD,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CACjC,CAAC;IAEF,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"widget-ask-bar.js","sourceRoot":"","sources":["../../../src/components/widgets/widget-ask-bar.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport {\n PromptInput,\n PromptInputBody,\n PromptInputFooter,\n PromptInputSubmit,\n PromptInputTextarea,\n PromptInputTools,\n} from '@ekanos/ui/ai-prompt-input';\nimport { Icon } from '@ekanos/ui/icon';\nimport { cn } from '@ekanos/ui/utils';\n\nimport { useAskAssistant } from '../../hooks/ask-assistant-context';\nimport type { WidgetAskContext } from '../../types/widget-ask-context';\nimport { AskIcon } from './ask-icon';\n\ninterface Props {\n context: WidgetAskContext;\n}\n\n/**\n * Widget footer tag + expanding prompt input.\n *\n * - closed: a quiet, always-visible tag row at the bottom of the widget\n * (\"Aperture icon · Ask about this · chevron up\")\n * - open: clicking the tag reveals an inline `PromptInput` (the canonical\n * AI input component) below it. The tag itself doubles as the\n * close affordance (chevron rotates).\n *\n * Dismisses on click-outside or Escape. Submitting sends the user's typed\n * question (or the widget's suggestedPrompt fallback) to the assistant dock.\n */\nexport function WidgetAskBar({ context }: Props) {\n const [isActive, setIsActive] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const askAssistant = useAskAssistant();\n\n const dismiss = useCallback(() => {\n setIsActive(false);\n }, []);\n\n const handleSubmit = useCallback(\n (message: { text: string }) => {\n const prompt = message.text.trim() || context.suggestedPrompt;\n askAssistant({ ...context, suggestedPrompt: prompt });\n dismiss();\n },\n [askAssistant, context, dismiss],\n );\n\n const handleSuggestion = useCallback(\n (prompt: string) => {\n askAssistant({ ...context, suggestedPrompt: prompt });\n dismiss();\n },\n [askAssistant, context, dismiss],\n );\n\n const suggestions = context.suggestedPrompts ?? [];\n\n useEffect(() => {\n if (!isActive) return;\n const onPointerDown = (e: PointerEvent) => {\n const el = containerRef.current;\n if (!el) return;\n if (el.contains(e.target as Node)) return;\n dismiss();\n };\n document.addEventListener('pointerdown', onPointerDown);\n return () => document.removeEventListener('pointerdown', onPointerDown);\n }, [dismiss, isActive]);\n\n return (\n <div\n ref={containerRef}\n className=\"border-border/60 shrink-0 border-t\"\n data-test=\"widget-ask-bar\"\n >\n <button\n type=\"button\"\n onClick={() => (isActive ? dismiss() : setIsActive(true))}\n aria-expanded={isActive}\n aria-label={\n isActive\n ? `Close ask input for ${context.title}`\n : `Ask about ${context.title}`\n }\n data-test=\"widget-ask-bar-toggle\"\n className={cn(\n 'group/tag hover:bg-accent/40 flex w-full items-center gap-2.5 px-4 py-2.5 text-left transition-colors sm:px-5',\n isActive && 'bg-accent/30',\n )}\n >\n <AskIcon\n className={cn(\n 'text-primary h-5 w-5 shrink-0 transition-transform duration-200',\n isActive && 'scale-110',\n )}\n />\n <span className=\"text-muted-foreground group-hover/tag:text-foreground flex-1 text-base font-medium transition-colors\">\n Ask about this\n </span>\n <Icon\n name=\"fa-light fa-chevron-up\"\n className={cn(\n 'text-muted-foreground/50 group-hover/tag:text-muted-foreground h-3.5 w-3.5 shrink-0 transition-transform duration-200',\n isActive && 'rotate-180',\n )}\n />\n </button>\n\n {isActive && (\n <div\n className=\"border-border/40 space-y-3 border-t p-3 sm:px-5\"\n data-test=\"widget-ask-bar-input\"\n >\n {suggestions.length > 0 && (\n <div\n className=\"flex flex-wrap gap-2\"\n data-test=\"widget-ask-bar-suggestions\"\n >\n {suggestions.map((s) => (\n <button\n key={s.label}\n type=\"button\"\n onClick={() => handleSuggestion(s.prompt)}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-3 py-1.5',\n 'border-primary/20 bg-primary/5 text-primary border text-xs font-medium',\n 'hover:bg-primary/10 hover:border-primary/40 transition-colors',\n 'focus-visible:ring-ring/60 focus-visible:ring-2 focus-visible:outline-none',\n )}\n aria-label={s.label}\n >\n <Icon\n name=\"fa-light fa-sparkles\"\n className=\"h-3.5 w-3.5 shrink-0\"\n />\n <span className=\"text-left\">{s.label}</span>\n </button>\n ))}\n </div>\n )}\n <PromptInput onSubmit={handleSubmit}>\n <PromptInputBody>\n <PromptInputTextarea\n autoFocus\n placeholder={`Ask about ${context.title.toLowerCase()}…`}\n aria-label={`Ask about ${context.title}`}\n onKeyDown={(e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n dismiss();\n }\n }}\n />\n </PromptInputBody>\n <PromptInputFooter>\n <PromptInputTools />\n <PromptInputSubmit data-test=\"widget-ask-bar-send\" />\n </PromptInputFooter>\n </PromptInput>\n </div>\n )}\n </div>\n );\n}\n"]}
|
|
@@ -29,10 +29,31 @@ export interface WidgetMeta {
|
|
|
29
29
|
className?: string;
|
|
30
30
|
/** Integration health for this widget's product, if known. */
|
|
31
31
|
health?: WidgetHealth;
|
|
32
|
+
/**
|
|
33
|
+
* Where the widget's full surface lives, e.g. the workspace a pinned widget
|
|
34
|
+
* came from. Set by the host only where following it leaves the current
|
|
35
|
+
* page; the header menu shows "Open in workspace" when present.
|
|
36
|
+
*/
|
|
37
|
+
openHref?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* What the widget's own data layer reports through `Widget.DataState`. The
|
|
41
|
+
* host cannot see a widget's queries, so every field is opt-in: the header
|
|
42
|
+
* menu's Refresh, the error view's Try again, and the "Updated …" footer each
|
|
43
|
+
* appear only when the widget supplies what they need.
|
|
44
|
+
*/
|
|
45
|
+
export interface WidgetData {
|
|
46
|
+
/** Epoch milliseconds of the last successful load; absent when unknown. */
|
|
47
|
+
updatedAt?: number;
|
|
48
|
+
/** A reload is in flight while data is already on screen. */
|
|
49
|
+
refreshing: boolean;
|
|
50
|
+
/** Reloads the widget's data. */
|
|
51
|
+
refresh?: () => void;
|
|
32
52
|
}
|
|
33
53
|
export interface WidgetContextValue {
|
|
34
54
|
state: WidgetState;
|
|
35
55
|
actions: WidgetActions;
|
|
36
56
|
meta: WidgetMeta;
|
|
57
|
+
data?: WidgetData;
|
|
37
58
|
}
|
|
38
59
|
export declare const WidgetContext: import("react").Context<WidgetContextValue | null>;
|