feat: app profile integrations UI

This commit is contained in:
Saumya 2025-07-11 10:13:57 +05:30
parent 3c920ba0aa
commit c183a812e4
3 changed files with 476 additions and 94 deletions

View File

@ -5,21 +5,31 @@ import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Skeleton } from '@/components/ui/skeleton';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { DataTable, DataTableColumn } from '@/components/ui/data-table';
import { Switch } from '@/components/ui/switch';
import {
AlertCircle,
Settings,
User,
Plus,
Search,
X
X,
Link,
Trash2,
Edit,
Loader2,
CheckCircle2,
XCircle,
RefreshCw,
ExternalLink
} from 'lucide-react';
import { usePipedreamProfiles } from '@/hooks/react-query/pipedream/use-pipedream-profiles';
import { usePipedreamProfiles, useCreatePipedreamProfile, useUpdatePipedreamProfile, useDeletePipedreamProfile, useConnectPipedreamProfile } from '@/hooks/react-query/pipedream/use-pipedream-profiles';
import { usePipedreamApps } from '@/hooks/react-query/pipedream/use-pipedream';
import { CredentialProfileManager } from '@/components/agents/pipedream/credential-profile-manager';
import { PipedreamRegistry } from '@/components/agents/pipedream/pipedream-registry';
import { PipedreamConnector } from '@/components/agents/pipedream/pipedream-connector';
import { useQueryClient } from '@tanstack/react-query';
import { pipedreamKeys } from '@/hooks/react-query/pipedream/keys';
import {
@ -29,26 +39,189 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { cn } from '@/lib/utils';
import type { PipedreamProfile } from '@/components/agents/pipedream/pipedream-types';
import { toast } from 'sonner';
import type { PipedreamProfile, CreateProfileRequest } from '@/components/agents/pipedream/pipedream-types';
import type { PipedreamApp } from '@/hooks/react-query/pipedream/utils';
interface PipedreamConnectionsSectionProps {
onConnectNewApp?: (app: { app_slug: string; app_name: string }) => void;
}
interface AppTableProps {
appSlug: string;
appName: string;
profiles: PipedreamProfile[];
appImage?: string;
onManageProfile: (profile: PipedreamProfile) => void;
onConnect: (app: PipedreamApp) => void;
onProfileUpdate: (profile: PipedreamProfile, updates: any) => void;
onProfileDelete: (profile: PipedreamProfile) => void;
onProfileConnect: (profile: PipedreamProfile) => void;
isUpdating?: string;
isDeleting?: string;
isConnecting?: string;
allAppsData?: any;
}
const AppTable: React.FC<AppTableProps> = ({ appSlug, appName, profiles, appImage, onManageProfile }) => {
const AppTable: React.FC<AppTableProps> = ({
appSlug,
appName,
profiles,
appImage,
onConnect,
onProfileUpdate,
onProfileDelete,
onProfileConnect,
isUpdating,
isDeleting,
isConnecting,
allAppsData
}) => {
const [editingProfile, setEditingProfile] = useState<string | null>(null);
const [editName, setEditName] = useState('');
const [showDeleteDialog, setShowDeleteDialog] = useState<PipedreamProfile | null>(null);
const [showQuickCreate, setShowQuickCreate] = useState(false);
const [newProfileName, setNewProfileName] = useState('');
const [isCreating, setIsCreating] = useState(false);
const createProfile = useCreatePipedreamProfile();
const connectProfile = useConnectPipedreamProfile();
const registryApp = useMemo(() => {
return allAppsData?.apps?.find((app: PipedreamApp) =>
app.name_slug === appSlug ||
app.name.toLowerCase() === appName.toLowerCase()
);
}, [allAppsData, appSlug, appName]);
const mockPipedreamApp: PipedreamApp = useMemo(() => ({
id: appSlug,
name: appName,
name_slug: appSlug,
auth_type: "oauth",
description: `Connect to ${appName}`,
img_src: registryApp?.img_src || "",
custom_fields_json: registryApp?.custom_fields_json || "[]",
categories: registryApp?.categories || [],
featured_weight: 0,
connect: {
allowed_domains: registryApp?.connect?.allowed_domains || null,
base_proxy_target_url: registryApp?.connect?.base_proxy_target_url || "",
proxy_enabled: registryApp?.connect?.proxy_enabled || false,
},
}), [appSlug, appName, registryApp]);
const handleQuickCreate = async () => {
if (!newProfileName.trim()) {
toast.error('Please enter a profile name');
return;
}
setIsCreating(true);
try {
const request: CreateProfileRequest = {
profile_name: newProfileName.trim(),
app_slug: appSlug,
app_name: appName,
is_default: profiles.length === 0,
};
const newProfile = await createProfile.mutateAsync(request);
// Auto-connect the new profile
await connectProfile.mutateAsync({
profileId: newProfile.profile_id,
app: appSlug,
});
setNewProfileName('');
setShowQuickCreate(false);
toast.success('Profile created and connected!');
} catch (error) {
console.error('Error creating profile:', error);
} finally {
setIsCreating(false);
}
};
const handleEdit = (profile: PipedreamProfile) => {
setEditingProfile(profile.profile_id);
setEditName(profile.profile_name);
};
const handleSaveEdit = async (profile: PipedreamProfile) => {
if (!editName.trim()) return;
await onProfileUpdate(profile, { profile_name: editName.trim() });
setEditingProfile(null);
setEditName('');
};
const handleCancelEdit = () => {
setEditingProfile(null);
setEditName('');
};
const columns: DataTableColumn<PipedreamProfile>[] = [
{
id: 'name',
header: 'Profile Name',
width: 'w-1/2',
width: 'w-1/3',
cell: (profile) => (
<div className="flex items-center gap-2">
<span className="font-medium">{profile.profile_name}</span>
{editingProfile === profile.profile_id ? (
<div className="flex items-center gap-2 w-full">
<Input
value={editName}
onChange={(e) => setEditName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSaveEdit(profile);
if (e.key === 'Escape') handleCancelEdit();
}}
className="h-8 text-sm"
autoFocus
/>
<Button
size="sm"
onClick={() => handleSaveEdit(profile)}
disabled={isUpdating === profile.profile_id}
className="h-8 px-2"
>
{isUpdating === profile.profile_id ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<CheckCircle2 className="h-3 w-3" />
)}
</Button>
<Button
size="sm"
variant="outline"
onClick={handleCancelEdit}
className="h-8 px-2"
>
<X className="h-3 w-3" />
</Button>
</div>
) : (
<span className="font-medium">{profile.profile_name}</span>
)}
</div>
),
},
@ -69,6 +242,11 @@ const AppTable: React.FC<AppTableProps> = ({ appSlug, appName, profiles, appImag
Disconnected
</div>
)}
{profile.is_default && (
<Badge variant="outline" className="text-xs">
Default
</Badge>
)}
{!profile.is_active && (
<Badge variant="outline" className="text-xs">
Inactive
@ -80,22 +258,81 @@ const AppTable: React.FC<AppTableProps> = ({ appSlug, appName, profiles, appImag
{
id: 'actions',
header: 'Actions',
width: 'w-1/4',
width: 'w-1/3',
headerClassName: 'text-right',
className: 'text-right',
cell: (profile) => (
<Button
size="sm"
variant="ghost"
onClick={(e) => {
e.stopPropagation();
onManageProfile(profile);
}}
className="h-8 px-3 text-xs"
>
<Settings className="h-4 w-4 mr-1" />
Manage
</Button>
<div className="flex items-center justify-end gap-2">
{!profile.is_connected && (
<Button
size="sm"
variant="ghost"
onClick={() => onProfileConnect(profile)}
disabled={isConnecting === profile.profile_id}
className="h-8 px-2 text-xs"
>
{isConnecting === profile.profile_id ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<Link className="h-3 w-3" />
)}
Connect
</Button>
)}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
variant="ghost"
className="h-8 w-8 p-0"
>
<Settings className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => handleEdit(profile)}>
<Edit className="h-4 w-4" />
Edit Name
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onProfileUpdate(profile, { is_default: !profile.is_default })}
>
<CheckCircle2 className="h-4 w-4" />
{profile.is_default ? 'Remove Default' : 'Set as Default'}
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => onProfileUpdate(profile, { is_active: !profile.is_active })}
>
{profile.is_active ? (
<>
<XCircle className="h-4 w-4" />
Deactivate
</>
) : (
<>
<CheckCircle2 className="h-4 w-4" />
Activate
</>
)}
</DropdownMenuItem>
{profile.is_connected && (
<DropdownMenuItem onClick={() => onProfileConnect(profile)}>
<RefreshCw className="h-4 w-4" />
Reconnect
</DropdownMenuItem>
)}
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => setShowDeleteDialog(profile)}
className="text-destructive"
>
<Trash2 className="h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
),
},
];
@ -131,6 +368,61 @@ const AppTable: React.FC<AppTableProps> = ({ appSlug, appName, profiles, appImag
</p>
</div>
</div>
<div className="flex items-center gap-2">
{showQuickCreate ? (
<div className="flex items-center gap-2">
<Input
placeholder="Profile name"
value={newProfileName}
onChange={(e) => setNewProfileName(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleQuickCreate();
if (e.key === 'Escape') {
setShowQuickCreate(false);
setNewProfileName('');
}
}}
className="h-8 text-sm w-32"
autoFocus
/>
<Button
size="sm"
onClick={handleQuickCreate}
disabled={isCreating}
className="h-8 px-2"
>
{isCreating ? (
<Loader2 className="h-3 w-3 animate-spin" />
) : (
<CheckCircle2 className="h-3 w-3" />
)}
</Button>
<Button
size="sm"
variant="outline"
onClick={() => {
setShowQuickCreate(false);
setNewProfileName('');
}}
className="h-8 px-2"
>
<X className="h-3 w-3" />
</Button>
</div>
) : (
<>
<Button
size="sm"
variant="link"
onClick={() => onConnect(mockPipedreamApp)}
>
<Plus className="h-3 w-3" />
New Profile
</Button>
</>
)}
</div>
</div>
<DataTable
@ -139,48 +431,114 @@ const AppTable: React.FC<AppTableProps> = ({ appSlug, appName, profiles, appImag
emptyMessage={`No ${appName} profiles found`}
className="bg-card border rounded-lg"
/>
<AlertDialog open={!!showDeleteDialog} onOpenChange={(open) => !open && setShowDeleteDialog(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Profile</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete "{showDeleteDialog?.profile_name}"? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
if (showDeleteDialog) {
onProfileDelete(showDeleteDialog);
setShowDeleteDialog(null);
}
}}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};
interface PipedreamConnectionsSectionProps {
onConnectNewApp?: (app: { app_slug: string; app_name: string }) => void;
}
export const PipedreamConnectionsSection: React.FC<PipedreamConnectionsSectionProps> = ({
onConnectNewApp
}) => {
const [showProfileManager, setShowProfileManager] = useState(false);
const [showAppBrowser, setShowAppBrowser] = useState(false);
const [selectedAppForProfile, setSelectedAppForProfile] = useState<{ app_slug: string; app_name: string } | null>(null);
const [selectedProfile, setSelectedProfile] = useState<PipedreamProfile | null>(null);
const [showConnector, setShowConnector] = useState(false);
const [selectedApp, setSelectedApp] = useState<PipedreamApp | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [isUpdating, setIsUpdating] = useState<string | null>(null);
const [isDeleting, setIsDeleting] = useState<string | null>(null);
const [isConnecting, setIsConnecting] = useState<string | null>(null);
const queryClient = useQueryClient();
const { data: profiles, isLoading, error } = usePipedreamProfiles();
const { data: allAppsData } = usePipedreamApps(undefined, '');
const updateProfile = useUpdatePipedreamProfile();
const deleteProfile = useDeletePipedreamProfile();
const connectProfile = useConnectPipedreamProfile();
const handleAppSelect = (app: { app_slug: string; app_name: string }) => {
setShowAppBrowser(false);
setSelectedAppForProfile(app);
if (onConnectNewApp) {
onConnectNewApp(app);
}
setShowProfileManager(true);
};
const handleManageProfile = (profile: PipedreamProfile) => {
setSelectedProfile(profile);
setSelectedAppForProfile({ app_slug: profile.app_slug, app_name: profile.app_name });
setShowProfileManager(true);
const handleConnect = (app: PipedreamApp) => {
setSelectedApp(app);
setShowConnector(true);
};
const handleProfileManagerClose = () => {
setShowProfileManager(false);
setSelectedAppForProfile(null);
setSelectedProfile(null);
const handleConnectionComplete = (profileId: string, selectedTools: string[], appName: string, appSlug: string) => {
setShowConnector(false);
setSelectedApp(null);
toast.success(`Connected to ${appName}!`);
queryClient.invalidateQueries({ queryKey: pipedreamKeys.profiles.all() });
};
const handleProfileUpdate = async (profile: PipedreamProfile, updates: any) => {
setIsUpdating(profile.profile_id);
try {
await updateProfile.mutateAsync({
profileId: profile.profile_id,
request: updates,
});
toast.success('Profile updated successfully');
} catch (error) {
console.error('Error updating profile:', error);
} finally {
setIsUpdating(null);
}
};
const handleProfileDelete = async (profile: PipedreamProfile) => {
setIsDeleting(profile.profile_id);
try {
await deleteProfile.mutateAsync(profile.profile_id);
toast.success('Profile deleted successfully');
} catch (error) {
console.error('Error deleting profile:', error);
} finally {
setIsDeleting(null);
}
};
const handleProfileConnect = async (profile: PipedreamProfile) => {
setIsConnecting(profile.profile_id);
try {
await connectProfile.mutateAsync({
profileId: profile.profile_id,
app: profile.app_slug,
});
toast.success('Profile connected successfully');
} catch (error) {
console.error('Error connecting profile:', error);
} finally {
setIsConnecting(null);
}
};
const profilesByApp = profiles?.reduce((acc, profile) => {
const key = profile.app_slug;
if (!acc[key]) {
@ -311,7 +669,7 @@ export const PipedreamConnectionsSection: React.FC<PipedreamConnectionsSectionPr
placeholder="Search apps and profiles..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 pr-10 h-9"
className="pl-12 h-12 rounded-xl bg-muted/50 border-0 focus:bg-background focus:ring-2 focus:ring-primary/20 transition-all"
/>
{searchQuery && (
<Button
@ -324,8 +682,6 @@ export const PipedreamConnectionsSection: React.FC<PipedreamConnectionsSectionPr
</Button>
)}
</div>
{/* Results */}
{Object.keys(filteredProfilesByApp).length === 0 ? (
<Card className="border-dashed">
<CardContent className="p-8 text-center">
@ -359,7 +715,7 @@ export const PipedreamConnectionsSection: React.FC<PipedreamConnectionsSectionPr
return b.length - a.length;
})
.map(([appSlug, appProfiles]) => {
const registryApp = allAppsData?.apps?.find(app =>
const registryApp = allAppsData?.apps?.find((app: PipedreamApp) =>
app.name_slug === appSlug ||
app.name.toLowerCase() === appProfiles[0].app_name.toLowerCase()
);
@ -371,7 +727,14 @@ export const PipedreamConnectionsSection: React.FC<PipedreamConnectionsSectionPr
appName={appProfiles[0].app_name}
profiles={appProfiles}
appImage={registryApp?.img_src}
onManageProfile={handleManageProfile}
onConnect={handleConnect}
onProfileUpdate={handleProfileUpdate}
onProfileDelete={handleProfileDelete}
onProfileConnect={handleProfileConnect}
isUpdating={isUpdating}
isDeleting={isDeleting}
isConnecting={isConnecting}
allAppsData={allAppsData}
/>
);
})}
@ -379,7 +742,7 @@ export const PipedreamConnectionsSection: React.FC<PipedreamConnectionsSectionPr
)}
<Dialog open={showAppBrowser} onOpenChange={setShowAppBrowser}>
<DialogContent className="p-0 max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogContent className="p-0 max-w-6xl max-h-[90vh] overflow-y-auto">
<DialogHeader className="sr-only">
<DialogTitle>Browse Apps</DialogTitle>
<DialogDescription>
@ -387,38 +750,21 @@ export const PipedreamConnectionsSection: React.FC<PipedreamConnectionsSectionPr
</DialogDescription>
</DialogHeader>
<PipedreamRegistry
mode="simple"
mode='profile-only'
onAppSelected={handleAppSelect}
/>
</DialogContent>
</Dialog>
<Dialog open={showProfileManager} onOpenChange={handleProfileManagerClose}>
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{selectedAppForProfile
? `Manage ${selectedAppForProfile.app_name} Profiles`
: 'Manage Pipedream Profiles'
}
</DialogTitle>
<DialogDescription>
{selectedAppForProfile
? `Create and manage credential profiles for ${selectedAppForProfile.app_name}`
: 'Create and manage credential profiles for your Pipedream apps'
}
</DialogDescription>
</DialogHeader>
<CredentialProfileManager
appSlug={selectedAppForProfile?.app_slug}
appName={selectedAppForProfile?.app_name}
onProfileSelect={() => {
queryClient.invalidateQueries({ queryKey: pipedreamKeys.profiles.all() });
handleProfileManagerClose();
}}
/>
</DialogContent>
</Dialog>
{selectedApp && (
<PipedreamConnector
app={selectedApp}
open={showConnector}
onOpenChange={setShowConnector}
onComplete={handleConnectionComplete}
mode="profile-only"
/>
)}
</div>
);
};

View File

@ -222,14 +222,16 @@ export const PipedreamConnector: React.FC<PipedreamConnectorProps> = ({
<div>
<h3 className="text-lg font-semibold">Connect to {app.name}</h3>
<p className="text-sm text-muted-foreground">
{connectedProfiles.length > 0
? 'Select a profile or create a new one to connect different accounts'
: 'Create your first profile to get started'
{mode === 'profile-only'
? 'Create a new profile to connect your account'
: (connectedProfiles.length > 0
? 'Select a profile or create a new one to connect different accounts'
: 'Create your first profile to get started')
}
</p>
</div>
{connectedProfiles.length > 0 && !isCreatingProfile && (
{mode !== 'profile-only' && connectedProfiles.length > 0 && !isCreatingProfile && (
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="profile-select">Select Profile</Label>
@ -274,7 +276,7 @@ export const PipedreamConnector: React.FC<PipedreamConnectorProps> = ({
</div>
)}
{(connectedProfiles.length === 0 || isCreatingProfile) && (
{(mode === 'profile-only' || connectedProfiles.length === 0 || isCreatingProfile) && (
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="profile-name">Profile Name</Label>
@ -284,12 +286,12 @@ export const PipedreamConnector: React.FC<PipedreamConnectorProps> = ({
value={newProfileName}
onChange={handleProfileNameChange}
onKeyDown={handleKeyDown}
autoFocus={isCreatingProfile}
autoFocus={mode === 'profile-only' || isCreatingProfile}
/>
</div>
<div className="flex gap-3">
{isCreatingProfile && (
{mode !== 'profile-only' && isCreatingProfile && (
<Button
variant="outline"
onClick={() => {
@ -304,7 +306,7 @@ export const PipedreamConnector: React.FC<PipedreamConnectorProps> = ({
<Button
onClick={handleCreateProfile}
disabled={!newProfileName.trim() || isConnecting}
className="flex-1"
className={mode === 'profile-only' ? 'w-full' : 'flex-1'}
>
{isConnecting ? (
<>
@ -322,10 +324,10 @@ export const PipedreamConnector: React.FC<PipedreamConnectorProps> = ({
</div>
)}
{selectedProfileId && !isCreatingProfile && (
{mode !== 'profile-only' && selectedProfileId && !isCreatingProfile && (
<div className="pt-4 border-t">
<Button
onClick={mode === 'profile-only' ? handleProfileOnlyComplete : proceedToTools}
onClick={proceedToTools}
disabled={!selectedProfileId || isCompletingConnection}
className="w-full"
>
@ -334,11 +336,6 @@ export const PipedreamConnector: React.FC<PipedreamConnectorProps> = ({
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Connecting...
</>
) : mode === 'profile-only' ? (
<>
<Zap className="h-4 w-4" />
Complete Connection
</>
) : (
<>
Continue to Tools

View File

@ -17,7 +17,7 @@ interface PipedreamRegistryProps {
onProfileSelected?: (profile: PipedreamProfile) => void;
onToolsSelected?: (profileId: string, selectedTools: string[], appName: string, appSlug: string) => void;
onAppSelected?: (app: { app_slug: string; app_name: string }) => void;
mode?: 'full' | 'simple';
mode?: 'full' | 'simple' | 'profile-only';
onClose?: () => void;
}
@ -320,6 +320,33 @@ export const PipedreamRegistry: React.FC<PipedreamRegistryProps> = ({
<Plus className="h-3 w-3" />
Connect
</Button>
) : mode === 'profile-only' ? (
<>
{connectedProfiles.length > 0 ? (
<Button
size="sm"
onClick={(e) => {
e.stopPropagation();
handleConnectApp(app);
}}
variant="outline"
>
<Plus className="h-3 w-3" />
Add Profile
</Button>
) : (
<Button
size="sm"
onClick={(e) => {
e.stopPropagation();
handleConnectApp(app);
}}
>
<Plus className="h-3 w-3" />
Connect
</Button>
)}
</>
) : (
<>
{connectedProfiles.length > 0 ? (
@ -463,14 +490,19 @@ export const PipedreamRegistry: React.FC<PipedreamRegistryProps> = ({
<div className="flex items-center gap-3">
<div>
<div className="flex items-center gap-2">
<h1 className="text-xl font-semibold text-gray-900 dark:text-white">Integrations</h1>
<h1 className="text-xl font-semibold text-gray-900 dark:text-white">
Integrations
</h1>
<Badge variant="secondary" className="bg-blue-50 text-blue-700 border-blue-200 dark:border-blue-900 dark:bg-blue-900/20 dark:text-blue-400 text-xs">
<Sparkles className="h-3 w-3" />
New
</Badge>
</div>
<p className="text-gray-600 dark:text-gray-400 text-sm">
Connect your favorite tools and automate workflows
{mode === 'profile-only'
? 'Manage your accounts and credential profiles'
: 'Connect your favorite tools and automate workflows'
}
</p>
</div>
</div>
@ -495,7 +527,9 @@ export const PipedreamRegistry: React.FC<PipedreamRegistryProps> = ({
<div className="mb-6">
<div className="flex items-center gap-2 mb-3">
<User className="h-4 w-4 text-green-600 dark:text-green-400" />
<h2 className="text-md font-semibold text-gray-900 dark:text-white">My Connections</h2>
<h2 className="text-md font-semibold text-gray-900 dark:text-white">
{mode === 'profile-only' ? 'Connected Accounts' : 'My Connections'}
</h2>
<Badge variant="secondary" className="bg-green-50 text-green-700 border-green-200 dark:border-green-900 dark:bg-green-900/20 dark:text-green-400 text-xs">
{connectedApps.length}
</Badge>
@ -513,10 +547,12 @@ export const PipedreamRegistry: React.FC<PipedreamRegistryProps> = ({
<div className="mb-4">
<div className="flex items-center gap-2 mb-3">
<TrendingUp className="h-4 w-4 text-orange-600 dark:text-orange-400" />
<h2 className="text-md font-semibold text-gray-900 dark:text-white">Popular</h2>
<h2 className="text-md font-semibold text-gray-900 dark:text-white">
{mode === 'profile-only' ? 'Available Apps' : 'Popular'}
</h2>
<Badge variant="secondary" className="bg-orange-50 text-orange-700 border-orange-200 dark:border-orange-900 dark:bg-orange-900/20 dark:text-orange-400 text-xs">
<Star className="h-3 w-3 mr-1" />
Recommended
{mode === 'profile-only' ? 'Connect' : 'Recommended'}
</Badge>
</div>
</div>
@ -550,8 +586,10 @@ export const PipedreamRegistry: React.FC<PipedreamRegistryProps> = ({
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-2">No integrations found</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4 max-w-md mx-auto">
{selectedCategory !== 'All'
? `No integrations found in "${selectedCategory}" category. Try a different category or search term.`
: "Try adjusting your search criteria or browse our popular integrations."
? `No ${mode === 'profile-only' ? 'apps' : 'integrations'} found in "${selectedCategory}" category. Try a different category or search term.`
: mode === 'profile-only'
? "Try adjusting your search criteria or browse available apps."
: "Try adjusting your search criteria or browse our popular integrations."
}
</p>
<Button
@ -621,6 +659,7 @@ export const PipedreamRegistry: React.FC<PipedreamRegistryProps> = ({
open={showStreamlinedConnector}
onOpenChange={setShowStreamlinedConnector}
onComplete={handleConnectionComplete}
mode={mode === 'profile-only' ? 'profile-only' : 'full'}
/>
)}
</div>