@faskai/ui-commons 0.0.0-alpha.28 → 0.0.0-alpha.30

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.
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useState } from 'react';
4
4
  import { createFrontendClient } from '@pipedream/sdk/browser';
5
5
  import { useConnectionsApi } from './connections-api-provider';
6
+ import { launchNangoConnect } from './launch-nango-connect';
6
7
  import { ConnectedAppsList } from './connected-apps-list';
7
8
  import { AvailableAppsList } from './available-apps-list';
8
9
  import { AppSearchGrid } from './app-search-grid';
@@ -38,22 +39,37 @@ export function ConnectionsPanel({ variant, onConnectionsLoaded, }) {
38
39
  const appKey = 'appKey' in app ? app.appKey : app.name_slug;
39
40
  setConnecting(appKey);
40
41
  try {
41
- const { token, expiresAt } = await api.getConnectToken(appKey);
42
- const pd = createFrontendClient({
43
- externalUserId: 'user',
44
- tokenCallback: async () => ({
45
- token,
46
- expiresAt: new Date(expiresAt),
47
- connectLinkUrl: '',
48
- }),
49
- });
50
- await new Promise((resolve, reject) => {
51
- pd.connectAccount({
52
- app: appKey,
53
- onSuccess: () => resolve(),
54
- onError: (err) => reject(err),
55
- });
56
- });
42
+ const info = await api.connect(appKey);
43
+ switch (info.provider) {
44
+ case 'pipedream': {
45
+ const pd = createFrontendClient({
46
+ externalUserId: 'user',
47
+ tokenCallback: async () => ({
48
+ token: info.token,
49
+ expiresAt: new Date(info.expiresAt),
50
+ connectLinkUrl: '',
51
+ }),
52
+ });
53
+ await new Promise((resolve, reject) => {
54
+ pd.connectAccount({
55
+ app: appKey,
56
+ onSuccess: () => resolve(),
57
+ onError: (err) => reject(err),
58
+ });
59
+ });
60
+ break;
61
+ }
62
+ case 'nango': {
63
+ await launchNangoConnect(info.connectLink, appKey, api);
64
+ break;
65
+ }
66
+ default: {
67
+ // Exhaustiveness check — TypeScript will error if a new ConnectInfo
68
+ // variant is added without a matching branch here.
69
+ const _exhaustive = info;
70
+ throw new Error(`Unknown connect provider: ${JSON.stringify(_exhaustive)}`);
71
+ }
72
+ }
57
73
  await loadData();
58
74
  }
59
75
  finally {
@@ -0,0 +1,13 @@
1
+ import type { ConnectionsApi } from '../../types/connections-api';
2
+ /**
3
+ * Open a Nango Connect popup and resolve when the connection appears in the
4
+ * backend (Nango calls our server-side webhook independently — polling is
5
+ * just how the UI discovers that). Rejects if the popup is closed before a
6
+ * connection is established or if the overall flow exceeds the timeout.
7
+ *
8
+ * We intentionally do not rely on Nango's `postMessage` events: those are
9
+ * not delivered when popup blockers interfere or when the auth flow ends on
10
+ * a different origin. Polling the connections list is browser-agnostic and
11
+ * works regardless of whether the popup ever posts back.
12
+ */
13
+ export declare function launchNangoConnect(connectLink: string, appKey: string, api: ConnectionsApi): Promise<void>;
@@ -0,0 +1,63 @@
1
+ const POPUP_FEATURES = 'width=600,height=700,popup=yes';
2
+ const POLL_INTERVAL_MS = 1500;
3
+ const POLL_TIMEOUT_MS = 5 * 60 * 1000;
4
+ /**
5
+ * Open a Nango Connect popup and resolve when the connection appears in the
6
+ * backend (Nango calls our server-side webhook independently — polling is
7
+ * just how the UI discovers that). Rejects if the popup is closed before a
8
+ * connection is established or if the overall flow exceeds the timeout.
9
+ *
10
+ * We intentionally do not rely on Nango's `postMessage` events: those are
11
+ * not delivered when popup blockers interfere or when the auth flow ends on
12
+ * a different origin. Polling the connections list is browser-agnostic and
13
+ * works regardless of whether the popup ever posts back.
14
+ */
15
+ export async function launchNangoConnect(connectLink, appKey, api) {
16
+ const popup = window.open(connectLink, 'fask-connect', POPUP_FEATURES);
17
+ if (!popup) {
18
+ throw new Error('Connect popup was blocked by the browser. Allow popups for this site and try again.');
19
+ }
20
+ const startedAt = Date.now();
21
+ return new Promise((resolve, reject) => {
22
+ const finish = (action) => {
23
+ clearInterval(intervalId);
24
+ if (!popup.closed)
25
+ popup.close();
26
+ action();
27
+ };
28
+ const intervalId = window.setInterval(async () => {
29
+ if (Date.now() - startedAt > POLL_TIMEOUT_MS) {
30
+ finish(() => reject(new Error('Connect flow timed out after 5 minutes.')));
31
+ return;
32
+ }
33
+ try {
34
+ const conns = await api.getConnections();
35
+ const found = conns.find((c) => c.appKey === appKey && c.status === 'connected');
36
+ if (found) {
37
+ finish(() => resolve());
38
+ return;
39
+ }
40
+ }
41
+ catch {
42
+ // Transient fetch error — keep polling. A persistent failure will
43
+ // hit the timeout above.
44
+ }
45
+ if (popup.closed) {
46
+ // Popup closed without the connection appearing — treat as cancel.
47
+ // Run one more check first in case the connection landed between
48
+ // the last poll and the close.
49
+ try {
50
+ const conns = await api.getConnections();
51
+ if (conns.find((c) => c.appKey === appKey && c.status === 'connected')) {
52
+ finish(() => resolve());
53
+ return;
54
+ }
55
+ }
56
+ catch {
57
+ // Ignore — fall through to cancel.
58
+ }
59
+ finish(() => reject(new Error('Connect window was closed before authorization completed.')));
60
+ }
61
+ }, POLL_INTERVAL_MS);
62
+ });
63
+ }
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export { AvailableAppsList } from './components/connections/available-apps-list'
7
7
  export { AppSearchGrid } from './components/connections/app-search-grid';
8
8
  export { ConnectionsPanel } from './components/connections/connections-panel';
9
9
  export type { FaskConnection, FaskSearchableApp } from './types/connections';
10
- export type { ConnectionsApi } from './types/connections-api';
10
+ export type { ConnectionsApi, ConnectInfo } from './types/connections-api';
11
11
  export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, } from './components/ui/card';
12
12
  export { Badge, badgeVariants } from './components/ui/badge';
13
13
  export { Button, buttonVariants } from './components/ui/button';
@@ -1,10 +1,35 @@
1
1
  import type { FaskConnection, FaskSearchableApp } from './connections';
2
+ /**
3
+ * Information returned from `ConnectionsApi.connect()` to drive the
4
+ * provider-specific Connect UX in `ConnectionsPanel`.
5
+ *
6
+ * Each variant carries exactly the data its launch flow needs:
7
+ * - `pipedream` → token + expiresAt, passed to Pipedream's frontend SDK
8
+ * (`createFrontendClient` + `connectAccount`) which renders Pipedream's
9
+ * hosted popup.
10
+ * - `nango` → connectLink, a Nango Connect URL that we open in a popup and
11
+ * poll the connections list to detect completion.
12
+ *
13
+ * The discriminator (`provider`) MUST be present so `ConnectionsPanel` can
14
+ * branch exhaustively. Adding a new provider means adding a new variant here
15
+ * and a new branch in the panel — TypeScript will catch missing branches.
16
+ */
17
+ export type ConnectInfo = {
18
+ provider: 'pipedream';
19
+ token: string;
20
+ expiresAt: string;
21
+ } | {
22
+ provider: 'nango';
23
+ connectLink: string;
24
+ };
2
25
  export interface ConnectionsApi {
3
26
  getConnections(): Promise<FaskConnection[]>;
4
27
  searchApps(query: string): Promise<FaskSearchableApp[]>;
5
- getConnectToken(appKey: string): Promise<{
6
- token: string;
7
- expiresAt: string;
8
- }>;
28
+ /**
29
+ * Start a connect flow for `appKey`. Returns provider-specific launch info
30
+ * that `ConnectionsPanel` uses to render the appropriate UI (Pipedream
31
+ * popup vs Nango Connect popup).
32
+ */
33
+ connect(appKey: string): Promise<ConnectInfo>;
9
34
  disconnect(connectionId: string): Promise<void>;
10
35
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@faskai/ui-commons",
3
3
  "author": "Fask AI <arko@fask.ai>",
4
- "version": "0.0.0-alpha.28",
4
+ "version": "0.0.0-alpha.30",
5
5
  "description": "Common UI components for Fask applications.",
6
6
  "private": false,
7
7
  "main": "dist/index.js",
@@ -1,2 +0,0 @@
1
- export { GTMProvider } from './gtm/gtm-provider';
2
- export { default as BetaMarketingBanner } from './marketing/BetaMarketingBanner';
@@ -1,10 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.BetaMarketingBanner = exports.GTMProvider = void 0;
7
- var gtm_provider_1 = require("./gtm/gtm-provider");
8
- Object.defineProperty(exports, "GTMProvider", { enumerable: true, get: function () { return gtm_provider_1.GTMProvider; } });
9
- var BetaMarketingBanner_1 = require("./marketing/BetaMarketingBanner");
10
- Object.defineProperty(exports, "BetaMarketingBanner", { enumerable: true, get: function () { return __importDefault(BetaMarketingBanner_1).default; } });
@@ -1,7 +0,0 @@
1
- interface BetaMarketingBannerProps {
2
- variant?: 'full' | 'compact';
3
- showBetaOffer?: boolean;
4
- className?: string;
5
- }
6
- export default function BetaMarketingBanner({ variant, showBetaOffer, className, }: BetaMarketingBannerProps): import("react/jsx-runtime").JSX.Element;
7
- export {};
@@ -1,39 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = BetaMarketingBanner;
4
- const jsx_runtime_1 = require("react/jsx-runtime");
5
- const features = [
6
- {
7
- emoji: '⚡',
8
- title: 'Fastest AI Voice Chat',
9
- description: 'Industry-leading response times',
10
- },
11
- {
12
- emoji: '🧠',
13
- title: 'Self-Learning AI Agents',
14
- description: 'Fine-tune with your data or bring your own LLM',
15
- },
16
- {
17
- emoji: '👆',
18
- title: 'Easiest Setup',
19
- description: '1-click deployment worldwide',
20
- },
21
- {
22
- emoji: '🌍',
23
- title: 'Any Language',
24
- description: 'Human-sounding in 100+ languages',
25
- },
26
- {
27
- emoji: '🎥',
28
- title: 'Truly Multi-Modal',
29
- description: 'Voice, Video, and Text channels',
30
- },
31
- {
32
- emoji: '📈',
33
- title: 'Infinitely Scalable',
34
- description: 'Grows with your business needs',
35
- },
36
- ];
37
- function BetaMarketingBanner({ variant = 'full', showBetaOffer = true, className = '', }) {
38
- return ((0, jsx_runtime_1.jsxs)("div", { className: `relative overflow-hidden rounded-2xl bg-gradient-to-br from-gray-50 via-white to-gray-100 ${className}`, children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-gray-600 to-gray-800" }), (0, jsx_runtime_1.jsxs)("div", { className: "max-w-6xl mx-auto px-4 py-8 md:py-12", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center mb-8 md:mb-12", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-3xl md:text-4xl font-bold mb-4 text-gray-900", children: "Best-in-Class AI Features" }), (0, jsx_runtime_1.jsx)("p", { className: "text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed", children: "Experience the future of customer engagement with our cutting-edge AI technology" })] }), (0, jsx_runtime_1.jsx)("div", { className: `grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6 ${showBetaOffer ? 'mb-12' : ''}`, children: features.map((feature, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "group bg-white/70 backdrop-blur-sm rounded-xl p-6 border border-gray-100 hover:border-gray-200 hover:shadow-lg hover:-translate-y-1 transition-all duration-300 text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-14 h-14 rounded-full bg-gray-100 hover:bg-gray-200 flex items-center justify-center mx-auto mb-4 text-2xl shadow-md group-hover:shadow-lg transition-all duration-300", children: feature.emoji }), (0, jsx_runtime_1.jsx)("h3", { className: "text-lg font-semibold text-gray-900 mb-2", children: feature.title }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-600 leading-relaxed", children: feature.description })] }, index))) }), showBetaOffer && ((0, jsx_runtime_1.jsxs)("div", { className: "bg-gradient-to-br from-green-50 to-blue-50 rounded-2xl border-2 border-green-200 p-6 md:p-8 relative overflow-hidden", children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 bg-gradient-to-r from-green-100/20 to-blue-100/20 animate-pulse" }), (0, jsx_runtime_1.jsxs)("div", { className: "relative z-10", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-center mb-8", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex justify-center mb-4", children: (0, jsx_runtime_1.jsx)("span", { className: "inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-green-500 text-white animate-pulse", children: "LIMITED TIME" }) }), (0, jsx_runtime_1.jsx)("h3", { className: "text-2xl md:text-3xl font-bold text-gray-900 mb-2", children: "\uD83D\uDE80 Beta Launch Special" }), (0, jsx_runtime_1.jsx)("p", { className: "text-gray-600 max-w-lg mx-auto leading-relaxed", children: "Join our exclusive Beta program and get unprecedented access to our founders and AI technology." })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6 mb-8", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-white/60 backdrop-blur-sm rounded-xl p-6 border border-green-200 text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-4xl mb-3", children: "\uD83D\uDCB0" }), (0, jsx_runtime_1.jsx)("h4", { className: "text-lg font-semibold text-gray-900 mb-2", children: "50% OFF First Year" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-600 leading-relaxed", children: "Exclusive Beta pricing for early adopters. Save hundreds on your first year subscription." })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-white/60 backdrop-blur-sm rounded-xl p-6 border border-green-200 text-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-4xl mb-3", children: "\uD83E\uDD1D" }), (0, jsx_runtime_1.jsx)("h4", { className: "text-lg font-semibold text-gray-900 mb-2", children: "Priority Founder Support" }), (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-gray-600 leading-relaxed", children: "Get immediate, direct access to our founders for support, feature requests, and guidance." })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-center", children: [(0, jsx_runtime_1.jsx)("a", { href: "https://biz.fask.ai/billing?price-tier=beta&period=year", target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center px-8 py-3 rounded-lg bg-gray-900 hover:bg-gray-800 text-white font-semibold text-lg shadow-lg hover:shadow-xl hover:-translate-y-0.5 transition-all duration-300 border border-gray-900", children: "Subscribe Now" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xs text-gray-500 mt-4 opacity-80", children: "* Billed annually. Beta offer valid for limited customers only." })] })] })] }))] })] }));
39
- }