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:
@@ -28,3 +28,4 @@ export { aiSessionsApi } from './aiSessions'
|
||||
export { flowProposalsApi } from './flowProposals'
|
||||
export { flowpilotAnalyticsApi } from './flowpilotAnalytics'
|
||||
export { notificationsApi } from './notifications'
|
||||
export { publicTemplatesApi } from './publicTemplates'
|
||||
|
||||
54
frontend/src/api/publicTemplates.ts
Normal file
54
frontend/src/api/publicTemplates.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type {
|
||||
PublicGalleryResponse,
|
||||
PublicFlowDetail,
|
||||
PublicScriptDetail,
|
||||
GalleryCategory,
|
||||
} from '@/types/public-templates'
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000'
|
||||
|
||||
export const publicTemplatesApi = {
|
||||
async listGallery(params?: {
|
||||
category?: string
|
||||
type?: string
|
||||
sort?: string
|
||||
page?: number
|
||||
per_page?: number
|
||||
}): Promise<PublicGalleryResponse> {
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params?.category) searchParams.set('category', params.category)
|
||||
if (params?.type) searchParams.set('type', params.type)
|
||||
if (params?.sort) searchParams.set('sort', params.sort)
|
||||
if (params?.page) searchParams.set('page', String(params.page))
|
||||
if (params?.per_page) searchParams.set('per_page', String(params.per_page))
|
||||
const qs = searchParams.toString()
|
||||
const response = await fetch(`${API_URL}/api/v1/public/templates${qs ? `?${qs}` : ''}`)
|
||||
if (!response.ok) throw new Error('Failed to load gallery')
|
||||
return response.json()
|
||||
},
|
||||
|
||||
async getFlowDetail(id: string): Promise<PublicFlowDetail> {
|
||||
const response = await fetch(`${API_URL}/api/v1/public/templates/flows/${id}`)
|
||||
if (!response.ok) throw new Error('Failed to load flow detail')
|
||||
return response.json()
|
||||
},
|
||||
|
||||
async getScriptDetail(id: string): Promise<PublicScriptDetail> {
|
||||
const response = await fetch(`${API_URL}/api/v1/public/templates/scripts/${id}`)
|
||||
if (!response.ok) throw new Error('Failed to load script detail')
|
||||
return response.json()
|
||||
},
|
||||
|
||||
async listCategories(): Promise<GalleryCategory[]> {
|
||||
const response = await fetch(`${API_URL}/api/v1/public/templates/categories`)
|
||||
if (!response.ok) throw new Error('Failed to load categories')
|
||||
return response.json()
|
||||
},
|
||||
|
||||
async search(q: string): Promise<PublicGalleryResponse> {
|
||||
const searchParams = new URLSearchParams({ q })
|
||||
const response = await fetch(`${API_URL}/api/v1/public/templates/search?${searchParams}`)
|
||||
if (!response.ok) throw new Error('Search failed')
|
||||
return response.json()
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user