diff --git a/src/components/marketplace/PublishTeamModal.tsx b/src/components/marketplace/PublishTeamModal.tsx new file mode 100644 index 0000000..d46b581 --- /dev/null +++ b/src/components/marketplace/PublishTeamModal.tsx @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// Copyright (C) 2026 CrewForm + +import { useState, useEffect } from 'react' +import { X, Upload, Loader2, Plus, Tag, FileText, Eye, Edit3 } from 'lucide-react' +import { toast } from 'sonner' +import { useAuth } from '@/hooks/useAuth' +import { useSubmitTeam } from '@/hooks/useMarketplace' +import type { Team } from '@/types' + +interface PublishTeamModalProps { + team: Team | null + onClose: () => void +} + +const SUGGESTED_TAGS = [ + 'productivity', 'writing', 'coding', 'research', 'marketing', + 'data-analysis', 'customer-support', 'pipeline', 'orchestrator', + 'collaboration', 'automation', 'devops', 'sales', 'creative', +] + +export function PublishTeamModal({ team, onClose }: PublishTeamModalProps) { + const { user } = useAuth() + const submitMutation = useSubmitTeam() + const [tags, setTags] = useState([]) + const [newTag, setNewTag] = useState('') + const [readme, setReadme] = useState('') + const [readmePreview, setReadmePreview] = useState(false) + + // Pre-fill existing tags + useEffect(() => { + if (team?.marketplace_tags && team.marketplace_tags.length > 0) { + setTags(team.marketplace_tags) + } else { + setTags([]) + } + }, [team?.marketplace_tags]) + + // Pre-fill existing README + useEffect(() => { + setReadme(team?.marketplace_readme ?? '') + }, [team?.marketplace_readme]) + + if (!team) return null + + const addTag = (tag: string) => { + const normalized = tag.toLowerCase().trim() + if (normalized && !tags.includes(normalized)) { + setTags((prev) => [...prev, normalized]) + } + setNewTag('') + } + + const removeTag = (tag: string) => { + setTags((prev) => prev.filter((t) => t !== tag)) + } + + const handleSubmit = () => { + if (!user) return + submitMutation.mutate( + { teamId: team.id, tags, readme, userId: user.id }, + { + onSuccess: () => { + toast.success('Team submitted for review! You\'ll be notified when it\'s approved.') + onClose() + }, + }, + ) + } + + return ( +
+
+ {/* Header */} +
+
+ +

Publish Team to Marketplace

+
+ +
+ +
+ {/* Team info */} +
+

{team.name}

+

{team.description}

+ + {team.mode} mode + +
+ + {/* Note about included agents */} +
+

+ All member agents will be included in the published team. Users who install this team + will receive copies of each agent with their configurations. +

+
+ + {/* README */} +
+
+ + {readme.trim() && ( + + )} +
+ {readmePreview ? ( +
$1') + .replace(/^## (.*$)/gm, '

$1

') + .replace(/^# (.*$)/gm, '

$1

') + .replace(/\*\*(.*?)\*\*/g, '$1') + .replace(/\*(.*?)\*/g, '$1') + .replace(/`(.*?)`/g, '$1') + .replace(/^- (.*$)/gm, '
  • $1
  • ') + .replace(/\n/g, '
    ') + }} + /> + ) : ( +