@faskai/ui-commons 0.0.0-alpha.3 → 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.
- package/README.md +114 -14
- package/dist/components/connections/app-icon.d.ts +8 -0
- package/dist/components/connections/app-icon.js +21 -0
- package/dist/components/connections/app-search-grid.d.ts +10 -0
- package/dist/components/connections/app-search-grid.js +35 -0
- package/dist/components/connections/available-apps-list.d.ts +8 -0
- package/dist/components/connections/available-apps-list.js +15 -0
- package/dist/components/connections/connected-apps-list.d.ts +11 -0
- package/dist/components/connections/connected-apps-list.js +21 -0
- package/dist/components/connections/connections-api-provider.d.ts +9 -0
- package/dist/components/connections/connections-api-provider.js +14 -0
- package/dist/components/connections/connections-panel.d.ts +7 -0
- package/dist/components/connections/connections-panel.js +97 -0
- package/dist/components/connections/launch-nango-connect.d.ts +13 -0
- package/dist/components/connections/launch-nango-connect.js +63 -0
- package/dist/components/gtm/gtm-provider.d.ts +7 -0
- package/dist/components/gtm/gtm-provider.js +11 -0
- package/dist/components/layout/grid.d.ts +10 -0
- package/dist/components/layout/grid.js +11 -0
- package/dist/components/layout/row.d.ts +7 -0
- package/dist/components/layout/row.js +16 -0
- package/dist/components/layout/split-view.d.ts +6 -0
- package/dist/components/layout/split-view.js +6 -0
- package/dist/components/layout/stack.d.ts +6 -0
- package/dist/components/layout/stack.js +16 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/badge.js +22 -0
- package/dist/components/ui/button.d.ts +10 -0
- package/dist/components/ui/button.js +31 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/card.js +24 -0
- package/dist/data/betaMarketingData.d.ts +15 -0
- package/dist/data/betaMarketingData.js +32 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +20 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/types/connections-api.d.ts +35 -0
- package/dist/types/connections-api.js +1 -0
- package/dist/types/connections.d.ts +24 -0
- package/dist/types/connections.js +1 -0
- package/package.json +21 -16
package/README.md
CHANGED
|
@@ -1,20 +1,92 @@
|
|
|
1
1
|
# Fask UI Commons
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A collection of reusable React components and data exports for Fask applications.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* @/fask-biz-landing (B2B landing) is at fask.ai/business
|
|
7
|
-
* @/powpal-web-app (B2B dashboard) is at biz.fask.ai
|
|
5
|
+
## Data Exports
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
### Beta Marketing Content
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
Centralized marketing content for Beta features and offers that can be used across different projects with client-specific styling.
|
|
10
|
+
|
|
11
|
+
#### betaMarketingFeatures
|
|
12
|
+
|
|
13
|
+
Array of feature objects with emoji, title, and description:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { betaMarketingFeatures } from '@faskai/ui-commons';
|
|
17
|
+
|
|
18
|
+
// Each feature contains:
|
|
19
|
+
// { emoji: string, title: string, description: string }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Features included:**
|
|
23
|
+
- ⚡ **Fastest Voice** - Industry-leading response times
|
|
24
|
+
- 🧠 **Self-Learning** - Fine-tune with your data
|
|
25
|
+
- 👆 **Easiest Setup** - 1-click deployment worldwide
|
|
26
|
+
- 🌍 **Any Language** - Human-sounding in 100+ languages
|
|
27
|
+
- 🎥 **Truly Multi-Modal** - Voice, Video, and Text channels
|
|
28
|
+
- 📈 **Massively Scalable** - Unlimited concurrent calls and agents
|
|
29
|
+
|
|
30
|
+
#### betaOfferData
|
|
31
|
+
|
|
32
|
+
Beta launch offer content with structured data:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { betaOfferData } from '@faskai/ui-commons';
|
|
36
|
+
|
|
37
|
+
// Contains:
|
|
38
|
+
// - badge: "LIMITED TIME"
|
|
39
|
+
// - title: "🚀 Beta Launch Special"
|
|
40
|
+
// - description: string
|
|
41
|
+
// - callouts: Array<{emoji, title, description}>
|
|
42
|
+
// - cta: {text, url}
|
|
43
|
+
// - disclaimer: string
|
|
13
44
|
```
|
|
14
45
|
|
|
15
|
-
|
|
46
|
+
**Callouts included:**
|
|
47
|
+
- 💰 **50% OFF First Year** - Exclusive Beta pricing
|
|
48
|
+
- 🤝 **Priority Support** - Direct access to founders
|
|
16
49
|
|
|
17
|
-
|
|
50
|
+
#### Usage Example
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import { betaMarketingFeatures, betaOfferData } from '@faskai/ui-commons';
|
|
54
|
+
|
|
55
|
+
function BetaFeatures() {
|
|
56
|
+
return (
|
|
57
|
+
<section>
|
|
58
|
+
{/* Features */}
|
|
59
|
+
{betaMarketingFeatures.map((feature) => (
|
|
60
|
+
<div key={feature.title}>
|
|
61
|
+
<span>{feature.emoji}</span>
|
|
62
|
+
<h3>{feature.title}</h3>
|
|
63
|
+
<p>{feature.description}</p>
|
|
64
|
+
</div>
|
|
65
|
+
))}
|
|
66
|
+
|
|
67
|
+
{/* Beta Offer */}
|
|
68
|
+
<div>
|
|
69
|
+
<h2>{betaOfferData.title}</h2>
|
|
70
|
+
<p>{betaOfferData.description}</p>
|
|
71
|
+
{betaOfferData.callouts.map((callout) => (
|
|
72
|
+
<div key={callout.title}>
|
|
73
|
+
<span>{callout.emoji}</span>
|
|
74
|
+
<h4>{callout.title}</h4>
|
|
75
|
+
<p>{callout.description}</p>
|
|
76
|
+
</div>
|
|
77
|
+
))}
|
|
78
|
+
<a href={betaOfferData.cta.url}>{betaOfferData.cta.text}</a>
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Components
|
|
86
|
+
|
|
87
|
+
### GTMProvider
|
|
88
|
+
|
|
89
|
+
Google Tag Manager integration for tracking and analytics.
|
|
18
90
|
|
|
19
91
|
```tsx
|
|
20
92
|
import { GTMProvider } from '@faskai/ui-commons';
|
|
@@ -28,7 +100,24 @@ function App() {
|
|
|
28
100
|
}
|
|
29
101
|
```
|
|
30
102
|
|
|
31
|
-
|
|
103
|
+
## Installation
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm install @faskai/ui-commons
|
|
107
|
+
# or
|
|
108
|
+
yarn add @faskai/ui-commons
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Dependencies
|
|
112
|
+
|
|
113
|
+
- React 18+
|
|
114
|
+
- No styling dependencies (bring your own CSS framework)
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
Private - Fask AI
|
|
119
|
+
|
|
120
|
+
## Tracking Events
|
|
32
121
|
|
|
33
122
|
```tsx
|
|
34
123
|
import { trackEvent, trackConversion, trackLinkedInConversion } from '@faskai/ui-commons';
|
|
@@ -64,16 +153,27 @@ yarn build
|
|
|
64
153
|
yarn publish
|
|
65
154
|
```
|
|
66
155
|
|
|
67
|
-
##
|
|
156
|
+
## Design Philosophy
|
|
157
|
+
|
|
158
|
+
This package follows a **data-driven approach**:
|
|
159
|
+
|
|
160
|
+
- **Content centralization**: Marketing copy and data managed in one place
|
|
161
|
+
- **Styling flexibility**: Each project implements its own design system
|
|
162
|
+
- **Type safety**: Full TypeScript support for all exports
|
|
163
|
+
- **Zero styling opinions**: No CSS dependencies or styling conflicts
|
|
164
|
+
|
|
165
|
+
## Adding New Data
|
|
68
166
|
|
|
69
|
-
1. Create your
|
|
167
|
+
1. Create your data export in `src/data/`
|
|
70
168
|
2. Export it from `src/index.ts`
|
|
71
169
|
3. Build the package with `yarn build`
|
|
72
|
-
4.
|
|
170
|
+
4. Import and use in your client projects with custom styling
|
|
73
171
|
|
|
74
|
-
## Current
|
|
172
|
+
## Current Exports
|
|
75
173
|
|
|
76
174
|
- **GTMProvider**: Google Tag Manager integration
|
|
175
|
+
- **betaMarketingFeatures**: Beta feature content array
|
|
176
|
+
- **betaOfferData**: Beta offer structured data
|
|
77
177
|
- **trackEvent**: Custom event tracking
|
|
78
178
|
- **trackConversion**: Google Ads conversion tracking
|
|
79
179
|
- **trackLinkedInConversion**: LinkedIn Ads conversion tracking
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Blocks, Landmark, Building2, CreditCard, ShoppingCart, Calendar, Mail, MessageSquare, } from 'lucide-react';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
const iconMap = {
|
|
6
|
+
landmark: Landmark,
|
|
7
|
+
building2: Building2,
|
|
8
|
+
'credit-card': CreditCard,
|
|
9
|
+
'shopping-cart': ShoppingCart,
|
|
10
|
+
calendar: Calendar,
|
|
11
|
+
mail: Mail,
|
|
12
|
+
'message-square': MessageSquare,
|
|
13
|
+
};
|
|
14
|
+
const FallbackIcon = Blocks;
|
|
15
|
+
export function AppIcon({ src, icon, alt, className }) {
|
|
16
|
+
if (src) {
|
|
17
|
+
return _jsx("img", { src: src, alt: alt, className: cn('rounded', className) });
|
|
18
|
+
}
|
|
19
|
+
const Icon = (icon && iconMap[icon]) || FallbackIcon;
|
|
20
|
+
return _jsx(Icon, { className: cn('text-muted-foreground', className) });
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FaskSearchableApp } from '../../types/connections';
|
|
2
|
+
interface AppSearchGridProps {
|
|
3
|
+
connectedAppKeys: string[];
|
|
4
|
+
onSearch: (query: string) => Promise<FaskSearchableApp[]>;
|
|
5
|
+
onConnect: (app: FaskSearchableApp) => void;
|
|
6
|
+
connecting?: string | null;
|
|
7
|
+
initialApps?: FaskSearchableApp[];
|
|
8
|
+
}
|
|
9
|
+
export declare function AppSearchGrid({ connectedAppKeys, onSearch, onConnect, connecting, initialApps, }: AppSearchGridProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { Loader2 } from 'lucide-react';
|
|
5
|
+
import { AppIcon } from './app-icon';
|
|
6
|
+
import { Badge } from '../ui/badge';
|
|
7
|
+
import { Button } from '../ui/button';
|
|
8
|
+
export function AppSearchGrid({ connectedAppKeys, onSearch, onConnect, connecting, initialApps, }) {
|
|
9
|
+
const [query, setQuery] = useState('');
|
|
10
|
+
const [apps, setApps] = useState(initialApps || []);
|
|
11
|
+
const [searching, setSearching] = useState(false);
|
|
12
|
+
const debounceRef = useRef(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (debounceRef.current)
|
|
15
|
+
clearTimeout(debounceRef.current);
|
|
16
|
+
debounceRef.current = setTimeout(async () => {
|
|
17
|
+
if (!query.trim()) {
|
|
18
|
+
setApps(initialApps || []);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
setSearching(true);
|
|
22
|
+
const results = await onSearch(query);
|
|
23
|
+
setApps(results);
|
|
24
|
+
setSearching(false);
|
|
25
|
+
}, 300);
|
|
26
|
+
return () => {
|
|
27
|
+
if (debounceRef.current)
|
|
28
|
+
clearTimeout(debounceRef.current);
|
|
29
|
+
};
|
|
30
|
+
}, [query, onSearch, initialApps]);
|
|
31
|
+
function isConnected(slug) {
|
|
32
|
+
return connectedAppKeys.includes(slug);
|
|
33
|
+
}
|
|
34
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium", children: "Connect Apps" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Search and connect apps for your agent." })] }), _jsx("input", { className: "border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border px-3 py-1 text-sm shadow-sm transition-colors focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50", placeholder: "Search apps (e.g. Slack, Google Sheets, HubSpot)...", value: query, onChange: (e) => setQuery(e.target.value) }), searching && (_jsxs("div", { className: "flex items-center gap-2 py-4", children: [_jsx(Loader2, { className: "size-4 animate-spin" }), _jsx("span", { className: "text-muted-foreground text-sm", children: "Searching..." })] })), apps.length > 0 && (_jsx("div", { className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-3", children: apps.map((app) => (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border p-3", children: [_jsx(AppIcon, { src: app.img_src, alt: app.name, className: "size-8" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-medium", children: app.name }), isConnected(app.name_slug) && (_jsx("span", { className: "text-muted-foreground text-xs", children: "Connected" }))] }), isConnected(app.name_slug) ? (_jsx(Badge, { children: "Connected" })) : (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => onConnect(app), disabled: connecting === app.name_slug, children: [connecting === app.name_slug && (_jsx(Loader2, { className: "animate-spin" })), "Connect"] }))] }, app.name_slug))) }))] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FaskConnection } from '../../types/connections';
|
|
2
|
+
interface AvailableAppsListProps {
|
|
3
|
+
connections: FaskConnection[];
|
|
4
|
+
onConnect: (connection: FaskConnection) => void;
|
|
5
|
+
connecting?: string | null;
|
|
6
|
+
}
|
|
7
|
+
export declare function AvailableAppsList({ connections, onConnect, connecting, }: AvailableAppsListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Link, Loader2 } from 'lucide-react';
|
|
4
|
+
import { AppIcon } from './app-icon';
|
|
5
|
+
import { Card, CardHeader, CardFooter } from '../ui/card';
|
|
6
|
+
import { Button } from '../ui/button';
|
|
7
|
+
export function AvailableAppsList({ connections, onConnect, connecting, }) {
|
|
8
|
+
const available = connections.filter((c) => c.status !== 'connected');
|
|
9
|
+
if (available.length === 0)
|
|
10
|
+
return null;
|
|
11
|
+
return (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: available.map((conn) => {
|
|
12
|
+
const isConnecting = connecting === conn.appKey;
|
|
13
|
+
return (_jsxs(Card, { className: "gap-4 py-4", children: [_jsxs(CardHeader, { className: "flex-row items-center gap-3", children: [_jsx("div", { className: "bg-background flex size-12 shrink-0 items-center justify-center rounded-lg border p-2", children: _jsx(AppIcon, { src: conn.app?.img, icon: conn.app?.icon, alt: conn.app?.name, className: "size-full object-contain" }) }), _jsx("p", { className: "min-w-0 flex-1 truncate text-base font-semibold", children: conn.app?.name })] }), _jsx(CardFooter, { children: _jsxs(Button, { className: "w-full", onClick: () => onConnect(conn), disabled: isConnecting, children: [isConnecting ? (_jsx(Loader2, { className: "animate-spin" })) : (_jsx(Link, {})), isConnecting ? 'Connecting...' : 'Connect'] }) })] }, conn.id || conn.appKey));
|
|
14
|
+
}) }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FaskConnection } from '../../types/connections';
|
|
2
|
+
interface ConnectedAppsListProps {
|
|
3
|
+
connections: FaskConnection[];
|
|
4
|
+
variant: 'cards' | 'badges';
|
|
5
|
+
onReimport?: (connection: FaskConnection) => void;
|
|
6
|
+
onDisconnect?: (connection: FaskConnection) => void;
|
|
7
|
+
reimporting?: string | null;
|
|
8
|
+
disconnecting?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export declare function ConnectedAppsList({ connections, variant, onReimport, onDisconnect, reimporting, disconnecting, }: ConnectedAppsListProps): import("react/jsx-runtime").JSX.Element | null;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Check, Loader2, RefreshCw, Unlink } from 'lucide-react';
|
|
4
|
+
import { AppIcon } from './app-icon';
|
|
5
|
+
import { Card, CardHeader, CardFooter, CardAction } from '../ui/card';
|
|
6
|
+
import { Badge } from '../ui/badge';
|
|
7
|
+
import { Button } from '../ui/button';
|
|
8
|
+
export function ConnectedAppsList({ connections, variant, onReimport, onDisconnect, reimporting, disconnecting, }) {
|
|
9
|
+
const connected = connections.filter((c) => c.status === 'connected');
|
|
10
|
+
if (connected.length === 0)
|
|
11
|
+
return null;
|
|
12
|
+
if (variant === 'badges') {
|
|
13
|
+
return (_jsx("div", { className: "flex flex-wrap gap-2", children: connected.map((conn) => (_jsxs(Badge, { children: [_jsx(AppIcon, { src: conn.app?.img, icon: conn.app?.icon, alt: conn.app?.name, className: "size-4" }), conn.app?.name] }, conn.appKey))) }));
|
|
14
|
+
}
|
|
15
|
+
// cards variant
|
|
16
|
+
return (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: connected.map((conn) => {
|
|
17
|
+
const isReimporting = reimporting === (conn.providerConnectionId || conn.appKey);
|
|
18
|
+
const isDisconnecting = disconnecting === (conn.id || conn.appKey);
|
|
19
|
+
return (_jsxs(Card, { className: "gap-4 py-4", children: [_jsxs(CardHeader, { className: "flex-row items-center gap-3", children: [_jsx("div", { className: "bg-background flex size-12 shrink-0 items-center justify-center rounded-lg border p-2", children: _jsx(AppIcon, { src: conn.app?.img, icon: conn.app?.icon, alt: conn.app?.name, className: "size-full object-contain" }) }), _jsx("p", { className: "min-w-0 flex-1 truncate text-base font-semibold", children: conn.app?.name }), _jsx(CardAction, { children: _jsxs(Badge, { variant: "outline", children: [_jsx(Check, { className: "size-3" }), "Connected"] }) })] }), _jsxs(CardFooter, { className: "gap-2", children: [onReimport && (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => onReimport(conn), disabled: isReimporting, children: [isReimporting ? (_jsx(Loader2, { className: "animate-spin" })) : (_jsx(RefreshCw, {})), isReimporting ? 'Reimporting...' : 'Reimport'] })), onDisconnect && (_jsxs(Button, { variant: "outline", size: "sm", className: "text-destructive hover:text-destructive", onClick: () => onDisconnect(conn), disabled: isDisconnecting, children: [isDisconnecting ? (_jsx(Loader2, { className: "animate-spin" })) : (_jsx(Unlink, {})), isDisconnecting ? 'Disconnecting...' : 'Disconnect'] }))] })] }, conn.id || conn.appKey));
|
|
20
|
+
}) }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ConnectionsApi } from '../../types/connections-api';
|
|
3
|
+
export declare function useConnectionsApi(): ConnectionsApi;
|
|
4
|
+
interface ConnectionsApiProviderProps {
|
|
5
|
+
api: ConnectionsApi;
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function ConnectionsApiProvider({ api, children, }: ConnectionsApiProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
const ConnectionsApiContext = createContext(null);
|
|
5
|
+
export function useConnectionsApi() {
|
|
6
|
+
const api = useContext(ConnectionsApiContext);
|
|
7
|
+
if (!api) {
|
|
8
|
+
throw new Error('useConnectionsApi must be used within a ConnectionsApiProvider');
|
|
9
|
+
}
|
|
10
|
+
return api;
|
|
11
|
+
}
|
|
12
|
+
export function ConnectionsApiProvider({ api, children, }) {
|
|
13
|
+
return (_jsx(ConnectionsApiContext.Provider, { value: api, children: children }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FaskConnection } from '../../types/connections';
|
|
2
|
+
interface ConnectionsPanelProps {
|
|
3
|
+
variant: 'full-page' | 'compact';
|
|
4
|
+
onConnectionsLoaded?: (connections: FaskConnection[]) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function ConnectionsPanel({ variant, onConnectionsLoaded, }: ConnectionsPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { createFrontendClient } from '@pipedream/sdk/browser';
|
|
5
|
+
import { useConnectionsApi } from './connections-api-provider';
|
|
6
|
+
import { launchNangoConnect } from './launch-nango-connect';
|
|
7
|
+
import { ConnectedAppsList } from './connected-apps-list';
|
|
8
|
+
import { AvailableAppsList } from './available-apps-list';
|
|
9
|
+
import { AppSearchGrid } from './app-search-grid';
|
|
10
|
+
import { Card } from '../ui/card';
|
|
11
|
+
export function ConnectionsPanel({ variant, onConnectionsLoaded, }) {
|
|
12
|
+
const api = useConnectionsApi();
|
|
13
|
+
const [connections, setConnections] = useState([]);
|
|
14
|
+
const [initialApps, setInitialApps] = useState([]);
|
|
15
|
+
const [loading, setLoading] = useState(true);
|
|
16
|
+
const [connecting, setConnecting] = useState(null);
|
|
17
|
+
const [disconnecting, setDisconnecting] = useState(null);
|
|
18
|
+
const loadData = useCallback(async () => {
|
|
19
|
+
const conns = await api.getConnections();
|
|
20
|
+
setConnections(conns);
|
|
21
|
+
onConnectionsLoaded?.(conns);
|
|
22
|
+
}, [api, onConnectionsLoaded]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
async function init() {
|
|
25
|
+
setLoading(true);
|
|
26
|
+
const [, apps] = await Promise.all([
|
|
27
|
+
loadData(),
|
|
28
|
+
api.searchApps(''),
|
|
29
|
+
]);
|
|
30
|
+
setInitialApps(apps);
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
init();
|
|
34
|
+
}, [loadData, api]);
|
|
35
|
+
const connectedAppKeys = connections
|
|
36
|
+
.filter((c) => c.status === 'connected')
|
|
37
|
+
.map((c) => c.appKey);
|
|
38
|
+
async function handleConnect(app) {
|
|
39
|
+
const appKey = 'appKey' in app ? app.appKey : app.name_slug;
|
|
40
|
+
setConnecting(appKey);
|
|
41
|
+
try {
|
|
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
|
+
}
|
|
73
|
+
await loadData();
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
setConnecting(null);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function handleDisconnect(conn) {
|
|
80
|
+
const key = conn.id || conn.appKey;
|
|
81
|
+
setDisconnecting(key);
|
|
82
|
+
try {
|
|
83
|
+
await api.disconnect(conn.id);
|
|
84
|
+
await loadData();
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
setDisconnecting(null);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (loading) {
|
|
91
|
+
return (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: [1, 2, 3].map((i) => (_jsx(Card, { className: "h-28 animate-pulse bg-muted/50" }, i))) }));
|
|
92
|
+
}
|
|
93
|
+
if (variant === 'compact') {
|
|
94
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsx(ConnectedAppsList, { connections: connections, variant: "badges", onDisconnect: handleDisconnect, disconnecting: disconnecting }), _jsx(AppSearchGrid, { connectedAppKeys: connectedAppKeys, onSearch: (q) => api.searchApps(q), onConnect: (app) => handleConnect(app), connecting: connecting, initialApps: initialApps })] }));
|
|
95
|
+
}
|
|
96
|
+
return (_jsxs("div", { className: "space-y-6", children: [_jsx(ConnectedAppsList, { connections: connections, variant: "cards", onDisconnect: handleDisconnect, disconnecting: disconnecting }), _jsx(AvailableAppsList, { connections: connections, onConnect: handleConnect, connecting: connecting }), _jsx(AppSearchGrid, { connectedAppKeys: connectedAppKeys, onSearch: (q) => api.searchApps(q), onConnect: (app) => handleConnect(app), connecting: connecting, initialApps: initialApps })] }));
|
|
97
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import Script from 'next/script';
|
|
3
|
+
export function GTMProvider({ gtmId, children }) {
|
|
4
|
+
return (_jsxs(_Fragment, { children: [_jsx(Script, { id: "gtm-head", strategy: "afterInteractive", children: `
|
|
5
|
+
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
|
6
|
+
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
|
7
|
+
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
|
8
|
+
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
|
9
|
+
})(window,document,'script','dataLayer','${gtmId}');
|
|
10
|
+
` }), _jsx("noscript", { children: _jsx("iframe", { src: `https://www.googletagmanager.com/ns.html?id=${gtmId}`, height: "0", width: "0", style: { display: 'none', visibility: 'hidden' } }) }), children] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
declare const GRID_STYLES: {
|
|
3
|
+
readonly 2: "grid-cols-1 gap-4 sm:grid-cols-2";
|
|
4
|
+
readonly 3: "grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3";
|
|
5
|
+
readonly 4: "grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3 xl:grid-cols-4";
|
|
6
|
+
};
|
|
7
|
+
declare function Grid({ columns, className, ...props }: React.ComponentProps<'div'> & {
|
|
8
|
+
columns: keyof typeof GRID_STYLES;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export { Grid };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
const GRID_STYLES = {
|
|
4
|
+
2: 'grid-cols-1 gap-4 sm:grid-cols-2',
|
|
5
|
+
3: 'grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3',
|
|
6
|
+
4: 'grid-cols-1 gap-3 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3 xl:grid-cols-4',
|
|
7
|
+
};
|
|
8
|
+
function Grid({ columns, className, ...props }) {
|
|
9
|
+
return (_jsx("div", { "data-slot": "grid", className: cn('grid', GRID_STYLES[columns], className), ...props }));
|
|
10
|
+
}
|
|
11
|
+
export { Grid };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
declare function Row({ gap, fill, wrap, className, ...props }: React.ComponentProps<'div'> & {
|
|
3
|
+
gap?: 0 | 1 | 1.5 | 2 | 3 | 4 | 6 | 8;
|
|
4
|
+
fill?: boolean;
|
|
5
|
+
wrap?: boolean;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export { Row };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
const GAP_MAP = {
|
|
4
|
+
0: 'gap-0',
|
|
5
|
+
1: 'gap-1',
|
|
6
|
+
1.5: 'gap-1.5',
|
|
7
|
+
2: 'gap-2',
|
|
8
|
+
3: 'gap-3',
|
|
9
|
+
4: 'gap-4',
|
|
10
|
+
6: 'gap-6',
|
|
11
|
+
8: 'gap-8',
|
|
12
|
+
};
|
|
13
|
+
function Row({ gap = 2, fill = false, wrap = false, className, ...props }) {
|
|
14
|
+
return (_jsx("div", { "data-slot": "row", className: cn('flex flex-row min-w-0 overflow-hidden items-center', GAP_MAP[gap], fill && 'flex-1', wrap && 'flex-wrap', className), ...props }));
|
|
15
|
+
}
|
|
16
|
+
export { Row };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
declare function SplitView({ desktop, mobile, className, ...props }: Omit<React.ComponentProps<'div'>, 'children'> & {
|
|
3
|
+
desktop: React.ReactNode;
|
|
4
|
+
mobile: React.ReactNode;
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export { SplitView };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
function SplitView({ desktop, mobile, className, ...props }) {
|
|
4
|
+
return (_jsxs("div", { "data-slot": "split-view", className: cn('flex min-h-0 flex-1 flex-col', className), ...props, children: [_jsx("div", { className: "hidden min-h-0 min-w-0 flex-1 overflow-hidden lg:flex", children: desktop }), _jsx("div", { className: "flex min-h-0 flex-1 flex-col lg:hidden", children: mobile })] }));
|
|
5
|
+
}
|
|
6
|
+
export { SplitView };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
const GAP_MAP = {
|
|
4
|
+
0: 'gap-0',
|
|
5
|
+
1: 'gap-1',
|
|
6
|
+
1.5: 'gap-1.5',
|
|
7
|
+
2: 'gap-2',
|
|
8
|
+
3: 'gap-3',
|
|
9
|
+
4: 'gap-4',
|
|
10
|
+
6: 'gap-6',
|
|
11
|
+
8: 'gap-8',
|
|
12
|
+
};
|
|
13
|
+
function Stack({ gap = 4, fill = false, className, ...props }) {
|
|
14
|
+
return (_jsx("div", { "data-slot": "stack", className: cn('flex flex-col min-h-0 overflow-hidden', GAP_MAP[gap], fill && 'flex-1', className), ...props }));
|
|
15
|
+
}
|
|
16
|
+
export { Stack };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
declare const badgeVariants: (props?: ({
|
|
4
|
+
variant?: "default" | "secondary" | "destructive" | "outline" | null | undefined;
|
|
5
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
|
+
declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & {
|
|
7
|
+
asChild?: boolean;
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
const badgeVariants = cva('inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', {
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
|
9
|
+
secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
|
10
|
+
destructive: 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
|
11
|
+
outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
variant: 'default',
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
function Badge({ className, variant, asChild = false, ...props }) {
|
|
19
|
+
const Comp = asChild ? Slot : 'span';
|
|
20
|
+
return (_jsx(Comp, { "data-slot": "badge", className: cn(badgeVariants({ variant }), className), ...props }));
|
|
21
|
+
}
|
|
22
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type VariantProps } from 'class-variance-authority';
|
|
3
|
+
declare const buttonVariants: (props?: ({
|
|
4
|
+
variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
|
|
5
|
+
size?: "icon" | "default" | "sm" | "lg" | null | undefined;
|
|
6
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
|
+
declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & {
|
|
8
|
+
asChild?: boolean;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
3
|
+
import { cva } from 'class-variance-authority';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", {
|
|
6
|
+
variants: {
|
|
7
|
+
variant: {
|
|
8
|
+
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
|
9
|
+
destructive: 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
|
10
|
+
outline: 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
|
11
|
+
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
|
|
12
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
|
13
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
14
|
+
},
|
|
15
|
+
size: {
|
|
16
|
+
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
|
17
|
+
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
|
18
|
+
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
|
19
|
+
icon: 'size-9',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
variant: 'default',
|
|
24
|
+
size: 'default',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
function Button({ className, variant, size, asChild = false, ...props }) {
|
|
28
|
+
const Comp = asChild ? Slot : 'button';
|
|
29
|
+
return (_jsx(Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size, className })), ...props }));
|
|
30
|
+
}
|
|
31
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
declare function Card({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
3
|
+
declare function CardHeader({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
declare function CardTitle({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
declare function CardDescription({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
declare function CardAction({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
declare function CardContent({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
declare function CardFooter({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../lib/utils';
|
|
3
|
+
function Card({ className, ...props }) {
|
|
4
|
+
return (_jsx("div", { "data-slot": "card", className: cn('bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm', className), ...props }));
|
|
5
|
+
}
|
|
6
|
+
function CardHeader({ className, ...props }) {
|
|
7
|
+
return (_jsx("div", { "data-slot": "card-header", className: cn('@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6', className), ...props }));
|
|
8
|
+
}
|
|
9
|
+
function CardTitle({ className, ...props }) {
|
|
10
|
+
return (_jsx("div", { "data-slot": "card-title", className: cn('leading-none font-semibold', className), ...props }));
|
|
11
|
+
}
|
|
12
|
+
function CardDescription({ className, ...props }) {
|
|
13
|
+
return (_jsx("div", { "data-slot": "card-description", className: cn('text-muted-foreground text-sm', className), ...props }));
|
|
14
|
+
}
|
|
15
|
+
function CardAction({ className, ...props }) {
|
|
16
|
+
return (_jsx("div", { "data-slot": "card-action", className: cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', className), ...props }));
|
|
17
|
+
}
|
|
18
|
+
function CardContent({ className, ...props }) {
|
|
19
|
+
return (_jsx("div", { "data-slot": "card-content", className: cn('px-6', className), ...props }));
|
|
20
|
+
}
|
|
21
|
+
function CardFooter({ className, ...props }) {
|
|
22
|
+
return (_jsx("div", { "data-slot": "card-footer", className: cn('flex items-center px-6 [.border-t]:pt-6', className), ...props }));
|
|
23
|
+
}
|
|
24
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const betaOfferData = {
|
|
2
|
+
badge: 'LIMITED TIME',
|
|
3
|
+
title: 'Beta Launch Special',
|
|
4
|
+
description: 'Join our exclusive Beta program and get direct access to our founders.',
|
|
5
|
+
callouts: [
|
|
6
|
+
{
|
|
7
|
+
emoji: '🌟',
|
|
8
|
+
title: 'Help Shape the Future',
|
|
9
|
+
description: 'Have significant influence in shaping our product roadmap and stay ahead of your competition.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
emoji: '🔧',
|
|
13
|
+
title: 'Deep Customizations',
|
|
14
|
+
description: 'Deeply customize AI agents on Fask to perform any task, enhance your CX and grow your business.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
emoji: '💰',
|
|
18
|
+
title: '50% Off First Year',
|
|
19
|
+
description: 'Exclusive Beta pricing for early adopters. Save hundreds on your first year subscription.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
emoji: '🤝',
|
|
23
|
+
title: 'Priority Support',
|
|
24
|
+
description: 'Get immediate, priority access to our core team for support, feature requests and guidance.',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
cta: {
|
|
28
|
+
text: 'Subscribe Now',
|
|
29
|
+
url: 'https://biz.fask.ai/billing?price-tier=beta&period=year',
|
|
30
|
+
},
|
|
31
|
+
disclaimer: 'Beta offer valid for limited customers only.',
|
|
32
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { GTMProvider } from './components/gtm/gtm-provider';
|
|
2
|
+
export { betaOfferData } from './data/betaMarketingData';
|
|
3
|
+
export { ConnectionsApiProvider, useConnectionsApi, } from './components/connections/connections-api-provider';
|
|
4
|
+
export { AppIcon } from './components/connections/app-icon';
|
|
5
|
+
export { ConnectedAppsList } from './components/connections/connected-apps-list';
|
|
6
|
+
export { AvailableAppsList } from './components/connections/available-apps-list';
|
|
7
|
+
export { AppSearchGrid } from './components/connections/app-search-grid';
|
|
8
|
+
export { ConnectionsPanel } from './components/connections/connections-panel';
|
|
9
|
+
export type { FaskConnection, FaskSearchableApp } from './types/connections';
|
|
10
|
+
export type { ConnectionsApi, ConnectInfo } from './types/connections-api';
|
|
11
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, } from './components/ui/card';
|
|
12
|
+
export { Badge, badgeVariants } from './components/ui/badge';
|
|
13
|
+
export { Button, buttonVariants } from './components/ui/button';
|
|
14
|
+
export { Stack } from './components/layout/stack';
|
|
15
|
+
export { Row } from './components/layout/row';
|
|
16
|
+
export { Grid } from './components/layout/grid';
|
|
17
|
+
export { SplitView } from './components/layout/split-view';
|
|
18
|
+
export { cn } from './lib/utils';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { GTMProvider } from './components/gtm/gtm-provider';
|
|
2
|
+
export { betaOfferData } from './data/betaMarketingData';
|
|
3
|
+
// Connections
|
|
4
|
+
export { ConnectionsApiProvider, useConnectionsApi, } from './components/connections/connections-api-provider';
|
|
5
|
+
export { AppIcon } from './components/connections/app-icon';
|
|
6
|
+
export { ConnectedAppsList } from './components/connections/connected-apps-list';
|
|
7
|
+
export { AvailableAppsList } from './components/connections/available-apps-list';
|
|
8
|
+
export { AppSearchGrid } from './components/connections/app-search-grid';
|
|
9
|
+
export { ConnectionsPanel } from './components/connections/connections-panel';
|
|
10
|
+
// UI primitives (shadcn)
|
|
11
|
+
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, } from './components/ui/card';
|
|
12
|
+
export { Badge, badgeVariants } from './components/ui/badge';
|
|
13
|
+
export { Button, buttonVariants } from './components/ui/button';
|
|
14
|
+
// Layout primitives
|
|
15
|
+
export { Stack } from './components/layout/stack';
|
|
16
|
+
export { Row } from './components/layout/row';
|
|
17
|
+
export { Grid } from './components/layout/grid';
|
|
18
|
+
export { SplitView } from './components/layout/split-view';
|
|
19
|
+
// Utilities
|
|
20
|
+
export { cn } from './lib/utils';
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
};
|
|
25
|
+
export interface ConnectionsApi {
|
|
26
|
+
getConnections(): Promise<FaskConnection[]>;
|
|
27
|
+
searchApps(query: string): Promise<FaskSearchableApp[]>;
|
|
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>;
|
|
34
|
+
disconnect(connectionId: string): Promise<void>;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface FaskConnection {
|
|
2
|
+
id?: string;
|
|
3
|
+
appKey: string;
|
|
4
|
+
provider: string;
|
|
5
|
+
providerConnectionId?: string;
|
|
6
|
+
status: 'connected' | 'available' | 'disconnected' | 'pending' | 'error';
|
|
7
|
+
app: {
|
|
8
|
+
name: string;
|
|
9
|
+
img?: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
};
|
|
12
|
+
actions?: {
|
|
13
|
+
key: string;
|
|
14
|
+
appKey: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
export interface FaskSearchableApp {
|
|
20
|
+
name_slug: string;
|
|
21
|
+
name: string;
|
|
22
|
+
img_src: string;
|
|
23
|
+
provider: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faskai/ui-commons",
|
|
3
3
|
"author": "Fask AI <arko@fask.ai>",
|
|
4
|
-
"version": "0.0.0-alpha.
|
|
5
|
-
"description": "Common UI components
|
|
4
|
+
"version": "0.0.0-alpha.30",
|
|
5
|
+
"description": "Common UI components for Fask applications.",
|
|
6
6
|
"private": false,
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
@@ -11,23 +11,28 @@
|
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"build": "tsc"
|
|
15
|
-
"dev": "tsc --watch",
|
|
16
|
-
"clean": "rm -rf dist",
|
|
17
|
-
"prepare": "yarn build"
|
|
14
|
+
"build": "tsc"
|
|
18
15
|
},
|
|
19
16
|
"peerDependencies": {
|
|
20
|
-
"
|
|
21
|
-
"react": "
|
|
22
|
-
"
|
|
17
|
+
"@pipedream/sdk": ">=2.3.0",
|
|
18
|
+
"@radix-ui/react-slot": ">=1.0.0",
|
|
19
|
+
"class-variance-authority": ">=0.7.0",
|
|
20
|
+
"clsx": ">=2.0.0",
|
|
21
|
+
"lucide-react": ">=0.300.0",
|
|
22
|
+
"next": ">=13.0.0",
|
|
23
|
+
"react": ">=18.0.0",
|
|
24
|
+
"tailwind-merge": ">=2.0.0"
|
|
23
25
|
},
|
|
24
26
|
"devDependencies": {
|
|
27
|
+
"@pipedream/sdk": "^2.3.6",
|
|
28
|
+
"@radix-ui/react-slot": "^1.1.0",
|
|
25
29
|
"@types/react": "^18.0.0",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
30
|
+
"class-variance-authority": "^0.7.0",
|
|
31
|
+
"clsx": "^2.1.0",
|
|
32
|
+
"lucide-react": "^0.400.0",
|
|
33
|
+
"next": "^14.0.0",
|
|
34
|
+
"tailwind-merge": "^2.2.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
31
36
|
},
|
|
32
|
-
"
|
|
33
|
-
}
|
|
37
|
+
"dependencies": {}
|
|
38
|
+
}
|