1- import React , { useState } from 'react' ;
2- import { Box , Typography , TextField , InputAdornment , Grid , Card , CardContent } from '@mui/material' ;
1+ import React , { useEffect , useState } from 'react' ;
2+ import { Box , Typography , TextField , InputAdornment , Grid , Card , CardContent , Alert , CircularProgress } from '@mui/material' ;
33import SearchIcon from '@mui/icons-material/Search' ;
4+ import { AdminAPIClient } from '../api/client' ;
45
56/**
67 * Minimal placeholder for the Admin Console Plugin Marketplace.
78 * - Strict TypeScript compliant (no unused vars)
89 * - Not wired into the App shell yet; safe to compile
910 * - No provider name checks; trait‑gated wiring will come in later PRs
1011 */
11- export default function PluginMarketplace ( ) : JSX . Element {
12+ type Props = { client : AdminAPIClient ; docsBase ?: string } ;
13+
14+ export default function PluginMarketplace ( { client, docsBase } : Props ) : JSX . Element {
1215 const [ query , setQuery ] = useState ( '' ) ;
16+ const [ loading , setLoading ] = useState ( false ) ;
17+ const [ plugins , setPlugins ] = useState < Array < { id : string ; name ?: string ; description ?: string } > > ( [ ] ) ;
18+ const [ disabled , setDisabled ] = useState ( false ) ;
19+
20+ useEffect ( ( ) => {
21+ let cancelled = false ;
22+ const run = async ( ) => {
23+ setLoading ( true ) ;
24+ try {
25+ const res = await fetch ( `${ window . location . origin } /admin/marketplace/plugins` , {
26+ headers : { 'X-API-Key' : ( client as any ) . apiKey || '' } ,
27+ } ) ;
28+ if ( res . status === 404 ) {
29+ if ( ! cancelled ) setDisabled ( true ) ;
30+ return ;
31+ }
32+ if ( ! res . ok ) throw new Error ( `HTTP ${ res . status } ` ) ;
33+ const data = await res . json ( ) ;
34+ if ( ! cancelled ) setPlugins ( Array . isArray ( data ?. plugins ) ? data . plugins : [ ] ) ;
35+ } catch {
36+ if ( ! cancelled ) setDisabled ( true ) ;
37+ } finally {
38+ if ( ! cancelled ) setLoading ( false ) ;
39+ }
40+ } ;
41+ run ( ) ;
42+ return ( ) => { cancelled = true ; } ;
43+ } , [ client ] ) ;
1344
1445 return (
1546 < Box sx = { { px : { xs : 1 , sm : 2 , md : 3 } , py : 2 } } >
@@ -32,19 +63,46 @@ export default function PluginMarketplace(): JSX.Element {
3263 sx = { { mb : 3 } }
3364 />
3465
66+ { disabled && (
67+ < Alert severity = "info" sx = { { mb : 2 } } >
68+ Plugin Marketplace is disabled. Enable by setting < code > ADMIN_MARKETPLACE_ENABLED=true</ code > .
69+ { docsBase && (
70+ < >
71+ { ' ' } Learn more at < a href = { `${ docsBase } /admin/marketplace` } target = "_blank" rel = "noreferrer" > docs</ a > .
72+ </ >
73+ ) }
74+ </ Alert >
75+ ) }
76+
77+ { loading && < CircularProgress size = { 20 } sx = { { mb : 2 } } /> }
78+
3579 < Grid container spacing = { 2 } >
3680 { /* Empty state placeholder; results will be populated in later PRs */ }
3781 < Grid item xs = { 12 } >
3882 < Card variant = "outlined" >
3983 < CardContent >
40- < Typography color = "text.secondary" >
41- Marketplace results will appear here. Use traits to gate provider‑specific UI.
42- </ Typography >
84+ { plugins . length === 0 ? (
85+ < Typography color = "text.secondary" >
86+ { disabled ? 'Marketplace is currently disabled.' : 'No plugins found.' }
87+ </ Typography >
88+ ) : (
89+ < >
90+ { plugins
91+ . filter ( p => ( query ? ( p . name || '' ) . toLowerCase ( ) . includes ( query . toLowerCase ( ) ) : true ) )
92+ . map ( p => (
93+ < Box key = { p . id } sx = { { mb : 1 } } >
94+ < Typography variant = "subtitle1" > { p . name || p . id } </ Typography >
95+ { p . description && (
96+ < Typography variant = "body2" color = "text.secondary" > { p . description } </ Typography >
97+ ) }
98+ </ Box >
99+ ) ) }
100+ </ >
101+ ) }
43102 </ CardContent >
44103 </ Card >
45104 </ Grid >
46105 </ Grid >
47106 </ Box >
48107 ) ;
49108}
50-
0 commit comments