Spaces:
Configuration error
Configuration error
| "use client" | |
| import { useState, useEffect } from "react" | |
| import Link from "next/link" | |
| import { useUser } from "@stackframe/stack" | |
| import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" | |
| import { Button } from "@/components/ui/button" | |
| import { Badge } from "@/components/ui/badge" | |
| import { Input } from "@/components/ui/input" | |
| import { | |
| Workflow, | |
| Search, | |
| Play, | |
| Star, | |
| User, | |
| Plus, | |
| Loader2, | |
| ArrowRight, | |
| Sparkles, | |
| Lock, | |
| Globe | |
| } from "lucide-react" | |
| interface WorkflowItem { | |
| id: string | |
| slug: string | |
| name: string | |
| description: string | null | |
| steps: { name: string }[] | |
| totalRuns: number | |
| starsCount: number | |
| visibility: string | |
| creatorId: string | null | |
| } | |
| // Featured workflow templates | |
| const FEATURED_TEMPLATES = [ | |
| { | |
| name: "Blog Post Creator", | |
| description: "Generate a complete blog post from a topic - research, write, and optimize", | |
| steps: 3, | |
| category: "Content" | |
| }, | |
| { | |
| name: "Social Media Campaign", | |
| description: "Create cohesive content for Twitter, LinkedIn, and Instagram", | |
| steps: 4, | |
| category: "Marketing" | |
| }, | |
| { | |
| name: "Product Launch Kit", | |
| description: "Generate product description, email, and social posts", | |
| steps: 3, | |
| category: "Business" | |
| }, | |
| { | |
| name: "Research & Summary", | |
| description: "Research a topic, summarize key points, create presentation", | |
| steps: 3, | |
| category: "Research" | |
| }, | |
| ] | |
| export function WorkflowLibrary() { | |
| const user = useUser() | |
| const [workflows, setWorkflows] = useState<WorkflowItem[]>([]) | |
| const [myWorkflows, setMyWorkflows] = useState<WorkflowItem[]>([]) | |
| const [loading, setLoading] = useState(true) | |
| const [loadingMy, setLoadingMy] = useState(true) | |
| const [searchQuery, setSearchQuery] = useState("") | |
| // Fetch public workflows | |
| useEffect(() => { | |
| const fetchWorkflows = async () => { | |
| try { | |
| const response = await fetch('/api/workflows?visibility=public&limit=20') | |
| if (response.ok) { | |
| const data = await response.json() | |
| setWorkflows(data.workflows || []) | |
| } | |
| } catch (error) { | |
| console.error('Failed to fetch workflows:', error) | |
| } finally { | |
| setLoading(false) | |
| } | |
| } | |
| fetchWorkflows() | |
| }, []) | |
| // Fetch user's own workflows (including private) | |
| useEffect(() => { | |
| const fetchMyWorkflows = async () => { | |
| if (!user?.id) { | |
| setLoadingMy(false) | |
| return | |
| } | |
| try { | |
| const response = await fetch(`/api/workflows?creatorId=${user.id}&limit=50`) | |
| if (response.ok) { | |
| const data = await response.json() | |
| setMyWorkflows(data.workflows || []) | |
| } | |
| } catch (error) { | |
| console.error('Failed to fetch my workflows:', error) | |
| } finally { | |
| setLoadingMy(false) | |
| } | |
| } | |
| fetchMyWorkflows() | |
| }, [user?.id]) | |
| const filteredWorkflows = workflows.filter(w => | |
| w.name.toLowerCase().includes(searchQuery.toLowerCase()) || | |
| w.description?.toLowerCase().includes(searchQuery.toLowerCase()) | |
| ) | |
| const filteredMyWorkflows = myWorkflows.filter(w => | |
| w.name.toLowerCase().includes(searchQuery.toLowerCase()) || | |
| w.description?.toLowerCase().includes(searchQuery.toLowerCase()) | |
| ) | |
| return ( | |
| <div className="container mx-auto px-4 py-8 max-w-7xl"> | |
| {/* Hero */} | |
| <div className="text-center mb-12"> | |
| <div className="inline-flex items-center justify-center h-16 w-16 rounded-2xl bg-linear-to-br from-primary to-accent mb-4"> | |
| <Workflow className="h-8 w-8 text-white" /> | |
| </div> | |
| <h1 className="text-4xl md:text-5xl font-serif font-medium mb-4"> | |
| Workflow <span className="text-gradient italic">Library</span> | |
| </h1> | |
| <p className="text-xl text-muted-foreground max-w-2xl mx-auto mb-8"> | |
| Discover and use community-created prompt chains | |
| </p> | |
| {/* Actions */} | |
| <div className="flex items-center justify-center gap-4 flex-wrap"> | |
| <Link href="/workflows"> | |
| <Button className="gap-2"> | |
| <Plus className="h-4 w-4" /> | |
| Create Workflow | |
| </Button> | |
| </Link> | |
| </div> | |
| </div> | |
| {/* Search */} | |
| <div className="relative max-w-md mx-auto mb-12"> | |
| <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> | |
| <Input | |
| placeholder="Search workflows..." | |
| value={searchQuery} | |
| onChange={(e) => setSearchQuery(e.target.value)} | |
| className="pl-10" | |
| /> | |
| </div> | |
| {/* My Workflows Section (for logged in users) */} | |
| {user && ( | |
| <div className="mb-12"> | |
| <h2 className="text-2xl font-serif font-medium mb-6 flex items-center gap-2"> | |
| <User className="h-6 w-6 text-primary" /> | |
| My Workflows | |
| </h2> | |
| {loadingMy ? ( | |
| <div className="flex items-center justify-center py-8"> | |
| <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> | |
| </div> | |
| ) : filteredMyWorkflows.length > 0 ? ( | |
| <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| {filteredMyWorkflows.map((workflow) => ( | |
| <Link href={`/workflows/${workflow.slug}`} key={workflow.id}> | |
| <Card className="h-full hover:border-primary/50 hover:shadow-lg transition-all cursor-pointer"> | |
| <CardContent className="p-5"> | |
| <div className="flex items-start justify-between mb-3"> | |
| <h3 className="font-semibold">{workflow.name}</h3> | |
| <div className="flex items-center gap-2"> | |
| <Badge | |
| variant={workflow.visibility === "public" ? "default" : "secondary"} | |
| className="gap-1" | |
| > | |
| {workflow.visibility === "public" ? ( | |
| <Globe className="h-3 w-3" /> | |
| ) : ( | |
| <Lock className="h-3 w-3" /> | |
| )} | |
| {workflow.visibility} | |
| </Badge> | |
| </div> | |
| </div> | |
| {workflow.description && ( | |
| <p className="text-sm text-muted-foreground mb-4 line-clamp-2"> | |
| {workflow.description} | |
| </p> | |
| )} | |
| <div className="flex items-center gap-4 text-sm text-muted-foreground"> | |
| <span className="text-xs"> | |
| {(workflow.steps as any[])?.length || 0} steps | |
| </span> | |
| <span className="flex items-center gap-1"> | |
| <Play className="h-3 w-3" /> | |
| {workflow.totalRuns} | |
| </span> | |
| <span className="flex items-center gap-1"> | |
| <Star className="h-3 w-3" /> | |
| {workflow.starsCount} | |
| </span> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : ( | |
| <Card className="border-dashed"> | |
| <CardContent className="p-8 text-center text-muted-foreground"> | |
| <p className="mb-4">You haven't created any workflows yet.</p> | |
| <Link href="/workflows"> | |
| <Button variant="outline" className="gap-2"> | |
| <Plus className="h-4 w-4" /> | |
| Create Your First Workflow | |
| </Button> | |
| </Link> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </div> | |
| )} | |
| {/* Featured Templates */} | |
| <div className="mb-12"> | |
| <h2 className="text-2xl font-serif font-medium mb-6 flex items-center gap-2"> | |
| <Sparkles className="h-6 w-6 text-primary" /> | |
| Featured Templates | |
| </h2> | |
| <div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4"> | |
| {FEATURED_TEMPLATES.map((template, index) => ( | |
| <Link href="/workflows" key={index}> | |
| <Card className="h-full hover:border-primary/50 hover:shadow-lg transition-all cursor-pointer"> | |
| <CardContent className="p-5"> | |
| <Badge variant="secondary" className="mb-3"> | |
| {template.category} | |
| </Badge> | |
| <h3 className="font-semibold mb-2">{template.name}</h3> | |
| <p className="text-sm text-muted-foreground mb-3"> | |
| {template.description} | |
| </p> | |
| <div className="flex items-center justify-between text-sm"> | |
| <span className="text-muted-foreground"> | |
| {template.steps} steps | |
| </span> | |
| <ArrowRight className="h-4 w-4 text-primary" /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </Link> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Community Workflows */} | |
| <div> | |
| <h2 className="text-2xl font-serif font-medium mb-6 flex items-center gap-2"> | |
| <Globe className="h-6 w-6 text-primary" /> | |
| Community Workflows | |
| </h2> | |
| {loading ? ( | |
| <div className="flex items-center justify-center py-12"> | |
| <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> | |
| </div> | |
| ) : filteredWorkflows.length > 0 ? ( | |
| <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| {filteredWorkflows.map((workflow) => ( | |
| <Link href={`/workflows/${workflow.slug}`} key={workflow.id}> | |
| <Card className="h-full hover:border-primary/50 hover:shadow-lg transition-all cursor-pointer"> | |
| <CardContent className="p-5"> | |
| <div className="flex items-start justify-between mb-3"> | |
| <h3 className="font-semibold">{workflow.name}</h3> | |
| <Badge variant="outline"> | |
| {(workflow.steps as any[])?.length || 0} steps | |
| </Badge> | |
| </div> | |
| {workflow.description && ( | |
| <p className="text-sm text-muted-foreground mb-4 line-clamp-2"> | |
| {workflow.description} | |
| </p> | |
| )} | |
| <div className="flex items-center gap-4 text-sm text-muted-foreground"> | |
| <span className="flex items-center gap-1"> | |
| <Play className="h-3 w-3" /> | |
| {workflow.totalRuns} | |
| </span> | |
| <span className="flex items-center gap-1"> | |
| <Star className="h-3 w-3" /> | |
| {workflow.starsCount} | |
| </span> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : ( | |
| <Card className="border-dashed"> | |
| <CardContent className="p-12 text-center text-muted-foreground"> | |
| <Workflow className="h-16 w-16 mx-auto mb-4 opacity-20" /> | |
| <p className="text-lg font-medium mb-2">No Public Workflows Yet</p> | |
| <p className="mb-4">Be the first to share a workflow with the community!</p> | |
| <Link href="/workflows"> | |
| <Button> | |
| <Plus className="h-4 w-4 mr-2" /> | |
| Create Workflow | |
| </Button> | |
| </Link> | |
| </CardContent> | |
| </Card> | |
| )} | |
| </div> | |
| </div> | |
| ) | |
| } | |