| 'use client' |
|
|
| import { useEffect, useState } from 'react' |
| import { Sidebar } from '@/components/sidebar' |
| import { api, type Integration } from '@/lib/api' |
| import { Search } from 'lucide-react' |
|
|
| const categories = ['All', 'Developer Tools', 'Communication', 'Productivity', 'CRM', 'Finance', 'Social', 'Other'] |
|
|
| export default function AppsPage() { |
| const [integrations, setIntegrations] = useState<Integration[]>([]) |
| const [loading, setLoading] = useState(true) |
| const [category, setCategory] = useState('All') |
| const [search, setSearch] = useState('') |
|
|
| useEffect(() => { |
| api.get<Integration[]>('/api/apps') |
| .then((data) => { setIntegrations(data); setLoading(false) }) |
| .catch(() => setLoading(false)) |
| }, []) |
|
|
| const filtered = integrations.filter((i) => { |
| const matchCat = category === 'All' || i.category === category |
| const matchSearch = !search || i.name.toLowerCase().includes(search.toLowerCase()) || i.description.toLowerCase().includes(search.toLowerCase()) |
| return matchCat && matchSearch |
| }) |
|
|
| return ( |
| <div className="flex min-h-screen"> |
| <Sidebar integrationCount={integrations.length} /> |
| <main className="flex-1 flex flex-col"> |
| <header className="h-14 bg-bg-secondary border-b border-border flex items-center px-6 justify-between"> |
| <h1 className="text-base font-medium">Apps</h1> |
| <div className="relative"> |
| <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-text-dim" /> |
| <input |
| className="pl-9 pr-4 py-1.5 bg-bg-tertiary border border-border rounded-lg text-sm text-text-primary placeholder:text-text-dim focus:outline-none focus:border-accent-cyan w-64" |
| placeholder="Search apps..." |
| value={search} |
| onChange={(e) => setSearch(e.target.value)} |
| /> |
| </div> |
| </header> |
| <div className="flex-1 p-6 overflow-auto"> |
| <div className="flex gap-2 mb-6 flex-wrap"> |
| {categories.map((c) => ( |
| <button key={c} onClick={() => setCategory(c)} |
| className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${category === c ? 'bg-accent-cyan text-bg-primary' : 'bg-bg-tertiary border border-border text-text-secondary hover:text-text-primary'}`} |
| >{c}</button> |
| ))} |
| </div> |
| {loading ? ( |
| <div className="grid grid-cols-4 gap-4"> |
| {Array.from({ length: 8 }).map((_, i) => ( |
| <div key={i} className="bg-bg-tertiary border border-border rounded-xl p-5 animate-pulse"> |
| <div className="flex items-center gap-3 mb-3"> |
| <div className="w-9 h-9 bg-bg-hover rounded-lg" /> |
| <div className="flex-1"><div className="h-3 bg-bg-hover rounded w-20 mb-1" /><div className="h-2 bg-bg-hover rounded w-14" /></div> |
| </div> |
| <div className="h-2 bg-bg-hover rounded w-full mb-2" /><div className="h-2 bg-bg-hover rounded w-3/4" /> |
| </div> |
| ))} |
| </div> |
| ) : filtered.length === 0 ? ( |
| <div className="text-center py-20"><p className="text-text-muted">No apps found</p></div> |
| ) : ( |
| <div className="grid grid-cols-4 gap-4"> |
| {filtered.map((i) => ( |
| <div key={i.id} className="bg-bg-tertiary border border-border rounded-xl p-5 hover:border-accent-cyan/40 transition-colors group"> |
| <div className="flex items-center gap-3 mb-3"> |
| <div className="w-10 h-10 bg-bg-secondary rounded-lg flex items-center justify-center overflow-hidden border border-border"> |
| <img src={i.logo_url} alt={i.name} className="w-6 h-6" onError={(e) => (e.currentTarget.style.display = 'none')} /> |
| </div> |
| <div> |
| <p className="text-sm font-semibold">{i.name}</p> |
| <p className="text-[11px] text-text-muted">{i.category}</p> |
| </div> |
| </div> |
| <p className="text-xs text-text-secondary leading-relaxed line-clamp-2 mb-4">{i.description}</p> |
| <div className="flex items-center justify-between pt-3 border-t border-border"> |
| <span className="text-[11px] text-text-muted">{i.tool_count} tools</span> |
| <span className={`text-[11px] px-2 py-0.5 rounded font-medium ${i.auth_type === 'oauth2' ? 'bg-accent-cyan/10 text-accent-cyanLight' : 'bg-accent-green/10 text-accent-green'}`}> |
| {i.auth_type} |
| </span> |
| </div> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| </main> |
| </div> |
| ) |
| } |