feat(gallery): add public templates gallery frontend (Tasks 4 & 5)
Add types, API client, page component, card components, detail modal, and /templates route for the public templates gallery. Uses raw fetch() for unauthenticated access, glass-card design system, and URL-synced filters with debounced search. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
96
frontend/src/components/public/FlowTemplateCard.tsx
Normal file
96
frontend/src/components/public/FlowTemplateCard.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { GitBranch, BarChart3, Layers } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { PublicFlowTemplate } from '@/types'
|
||||
|
||||
interface FlowTemplateCardProps {
|
||||
template: PublicFlowTemplate
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
const typeLabels: Record<string, string> = {
|
||||
troubleshooting: 'Troubleshooting',
|
||||
procedural: 'Project',
|
||||
maintenance: 'Maintenance',
|
||||
}
|
||||
|
||||
export function FlowTemplateCard({ template, onClick }: FlowTemplateCardProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="glass-card text-left w-full flex flex-col gap-3 p-5"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="font-heading text-foreground text-base font-semibold leading-tight line-clamp-2">
|
||||
{template.name}
|
||||
</h3>
|
||||
<span className="shrink-0">
|
||||
<GitBranch className="w-4 h-4 text-muted-foreground" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{template.description && (
|
||||
<p className="text-muted-foreground text-sm leading-relaxed line-clamp-2">
|
||||
{template.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{template.tags.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{template.tags.slice(0, 3).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="font-label text-[0.625rem] uppercase tracking-[0.1em] px-2 py-0.5 rounded-md bg-card border border-border text-muted-foreground"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{template.tags.length > 3 && (
|
||||
<span className="font-label text-[0.625rem] text-muted-foreground">
|
||||
+{template.tags.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4 mt-auto pt-2 border-t border-border">
|
||||
{template.category && (
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
|
||||
{template.category}
|
||||
</span>
|
||||
)}
|
||||
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground flex items-center gap-1">
|
||||
<Layers className="w-3 h-3" />
|
||||
{template.step_count} steps
|
||||
</span>
|
||||
{template.success_rate !== null && (
|
||||
<span
|
||||
className={cn(
|
||||
'font-label text-[0.625rem] uppercase tracking-[0.1em] flex items-center gap-1 ml-auto',
|
||||
template.success_rate >= 80
|
||||
? 'text-emerald-400'
|
||||
: template.success_rate >= 50
|
||||
? 'text-amber-400'
|
||||
: 'text-rose-500'
|
||||
)}
|
||||
>
|
||||
<BarChart3 className="w-3 h-3" />
|
||||
{Math.round(template.success_rate)}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<span className={cn(
|
||||
'font-label text-[0.625rem] px-2 py-0.5 rounded-md border',
|
||||
'bg-primary/5 border-primary/20 text-primary'
|
||||
)}>
|
||||
{typeLabels[template.tree_type] || template.tree_type}
|
||||
</span>
|
||||
<span className="font-label text-[0.625rem] text-[#5a6170]">
|
||||
{template.usage_count.toLocaleString()} uses
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user