url query param & share agent marketplace

This commit is contained in:
marko-kraemer 2025-08-06 12:14:25 -07:00
parent 2c00ab706b
commit 1d01571500
3 changed files with 79 additions and 30 deletions

View File

@ -22,7 +22,7 @@ import { MarketplaceTab } from '@/components/agents/custom-agents-page/marketpla
import { PublishDialog } from '@/components/agents/custom-agents-page/publish-dialog';
import { LoadingSkeleton } from '@/components/agents/custom-agents-page/loading-skeleton';
import { NewAgentDialog } from '@/components/agents/new-agent-dialog';
import { MarketplaceAgentPreviewDialog } from '@/components/agents/marketplace-agent-preview-dialog';
type ViewMode = 'grid' | 'list';
type AgentSortOption = 'name' | 'created_at' | 'updated_at' | 'tools_count';
@ -78,6 +78,7 @@ export default function AgentsPage() {
const [installingItemId, setInstallingItemId] = useState<string | null>(null);
const [selectedItem, setSelectedItem] = useState<MarketplaceTemplate | null>(null);
const [showInstallDialog, setShowInstallDialog] = useState(false);
const [showPreviewDialog, setShowPreviewDialog] = useState(false);
const [marketplaceFilter, setMarketplaceFilter] = useState<'all' | 'kortix' | 'community' | 'mine'>('all');
const [templatesActioningId, setTemplatesActioningId] = useState<string | null>(null);
@ -239,6 +240,18 @@ export default function AgentsPage() {
setMarketplacePage(1);
}, [marketplaceSearchQuery, marketplaceSelectedTags, marketplaceSortBy]);
// Handle shared agent URL parameter
useEffect(() => {
const agentId = searchParams.get('agent');
if (agentId && allMarketplaceItems.length > 0) {
const sharedAgent = allMarketplaceItems.find(agent => agent.id === agentId);
if (sharedAgent) {
setSelectedItem(sharedAgent);
setShowPreviewDialog(true);
}
}
}, [searchParams, allMarketplaceItems]);
const handleDeleteAgent = async (agentId: string) => {
try {
await deleteAgent(agentId);
@ -277,6 +290,35 @@ export default function AgentsPage() {
setShowInstallDialog(true);
};
const handlePreviewClose = () => {
setShowPreviewDialog(false);
setSelectedItem(null);
// Remove agent parameter from URL
const currentUrl = new URL(window.location.href);
if (currentUrl.searchParams.has('agent')) {
currentUrl.searchParams.delete('agent');
router.replace(currentUrl.pathname + (currentUrl.searchParams.toString() ? '?' + currentUrl.searchParams.toString() : ''), { scroll: false });
}
};
const handlePreviewInstall = (agent: MarketplaceTemplate) => {
setShowPreviewDialog(false);
setSelectedItem(agent);
setShowInstallDialog(true);
};
const handleAgentPreview = (agent: MarketplaceTemplate) => {
setSelectedItem(agent);
setShowPreviewDialog(true);
// Update URL with agent parameter for sharing
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set('agent', agent.id);
currentUrl.searchParams.set('tab', 'marketplace');
router.replace(currentUrl.toString(), { scroll: false });
};
const handleInstall = async (
item: MarketplaceTemplate,
instanceName?: string,
@ -501,7 +543,7 @@ export default function AgentsPage() {
<div className="container mx-auto max-w-7xl px-4 py-8">
<AgentsPageHeader />
</div>
<div className="sticky top-0 z-50 relative">
<div className="sticky top-0 z-50">
<div className="absolute inset-0 backdrop-blur-md" style={{
maskImage: 'linear-gradient(to bottom, black 0%, black 60%, transparent 100%)',
WebkitMaskImage: 'linear-gradient(to bottom, black 0%, black 60%, transparent 100%)'
@ -558,6 +600,7 @@ export default function AgentsPage() {
onDeleteTemplate={handleDeleteTemplate}
getItemStyling={getItemStyling}
currentUserId={user?.id}
onAgentPreview={handleAgentPreview}
/>
)}
</div>
@ -581,6 +624,14 @@ export default function AgentsPage() {
open={showNewAgentDialog}
onOpenChange={setShowNewAgentDialog}
/>
<MarketplaceAgentPreviewDialog
agent={selectedItem}
isOpen={showPreviewDialog}
onClose={handlePreviewClose}
onInstall={handlePreviewInstall}
isInstalling={installingItemId === selectedItem?.id}
/>
</div>
</div>
);

View File

@ -1,12 +1,12 @@
'use client';
import React, { useState } from 'react';
import React from 'react';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Skeleton } from '@/components/ui/skeleton';
import { SearchBar } from './search-bar';
import { MarketplaceSectionHeader } from './marketplace-section-header';
import { AgentCard } from './agent-card';
import { MarketplaceAgentPreviewDialog } from '@/components/agents/marketplace-agent-preview-dialog';
import type { MarketplaceTemplate } from '@/components/agents/installation/types';
interface MarketplaceTabProps {
@ -24,6 +24,7 @@ interface MarketplaceTabProps {
onDeleteTemplate?: (item: MarketplaceTemplate, e?: React.MouseEvent) => void;
getItemStyling: (item: MarketplaceTemplate) => { avatar: string; color: string };
currentUserId?: string;
onAgentPreview?: (agent: MarketplaceTemplate) => void;
}
export const MarketplaceTab = ({
@ -40,25 +41,13 @@ export const MarketplaceTab = ({
onInstallClick,
onDeleteTemplate,
getItemStyling,
currentUserId
currentUserId,
onAgentPreview
}: MarketplaceTabProps) => {
const [previewAgent, setPreviewAgent] = useState<MarketplaceTemplate | null>(null);
const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
const handleAgentClick = (item: MarketplaceTemplate) => {
setPreviewAgent(item);
setPreviewDialogOpen(true);
};
const handlePreviewClose = () => {
setPreviewDialogOpen(false);
setPreviewAgent(null);
};
const handleInstallFromPreview = (agent: MarketplaceTemplate) => {
onInstallClick(agent);
setPreviewDialogOpen(false);
setPreviewAgent(null);
if (onAgentPreview) {
onAgentPreview(item);
}
};
return (
@ -174,14 +163,6 @@ export const MarketplaceTab = ({
</div>
)}
</div>
<MarketplaceAgentPreviewDialog
agent={previewAgent}
isOpen={previewDialogOpen}
onClose={handlePreviewClose}
onInstall={handleInstallFromPreview}
isInstalling={installingItemId === previewAgent?.id}
/>
</div>
);
};

View File

@ -6,7 +6,8 @@ import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Bot, Download, Wrench, Plug, Tag, User, Calendar, Loader2 } from 'lucide-react';
import { Bot, Download, Wrench, Plug, Tag, User, Calendar, Loader2, Share } from 'lucide-react';
import { toast } from 'sonner';
import type { MarketplaceTemplate } from '@/components/agents/installation/types';
import { AGENTPRESS_TOOL_DEFINITIONS } from '@/components/agents/tools';
import { useComposioToolkitIcon } from '@/hooks/react-query/composio/use-composio';
@ -111,6 +112,18 @@ export const MarketplaceAgentPreviewDialog: React.FC<MarketplaceAgentPreviewDial
onInstall(agent);
};
const handleShare = () => {
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set('agent', agent.id);
currentUrl.searchParams.set('tab', 'marketplace');
navigator.clipboard.writeText(currentUrl.toString()).then(() => {
toast.success('Share link copied to clipboard!');
}).catch(() => {
toast.error('Failed to copy link to clipboard');
});
};
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
@ -270,6 +283,10 @@ export const MarketplaceAgentPreviewDialog: React.FC<MarketplaceAgentPreviewDial
</>
)}
</Button>
<Button variant="outline" onClick={handleShare}>
<Share className="h-4 w-4" />
Share
</Button>
<Button variant="outline" onClick={onClose}>
Close
</Button>