@@ -25,6 +25,8 @@ import {
2525 Card ,
2626 CardContent ,
2727 Button ,
28+ useToast ,
29+ ToastContainer ,
2830} from '@mcpmux/ui' ;
2931import type { OAuthClient , UpdateClientRequest } from '@/lib/api/gateway' ;
3032import { listOAuthClients , updateOAuthClient , deleteOAuthClient } from '@/lib/api/gateway' ;
@@ -97,8 +99,7 @@ export default function ClientsPage() {
9799 // Panel state
98100 const [ selectedClient , setSelectedClient ] = useState < OAuthClient | null > ( null ) ;
99101
100- // Toast notification for reconnects
101- const [ toast , setToast ] = useState < { message : string ; type : 'info' | 'success' } | null > ( null ) ;
102+ const { toasts, success, error : showError , info, dismiss } = useToast ( ) ;
102103
103104 // Edit state
104105 const [ editAlias , setEditAlias ] = useState ( '' ) ;
@@ -264,8 +265,7 @@ export default function ClientsPage() {
264265 // Show toast for reconnections (silent approval)
265266 if ( event . payload . action === 'reconnected' ) {
266267 const name = event . payload . client_name || event . payload . client_id ;
267- setToast ( { message : `${ name } connected` , type : 'info' } ) ;
268- setTimeout ( ( ) => setToast ( null ) , 3000 ) ;
268+ info ( 'Client connected' , `${ name } connected` ) ;
269269 }
270270 } ) ;
271271
@@ -309,17 +309,24 @@ export default function ClientsPage() {
309309 const toggleFeatureSetGrant = async ( featureSetId : string ) => {
310310 if ( ! selectedClient || ! activeSpace ) return ;
311311
312+ const featureSet = availableFeatureSets . find ( fs => fs . id === featureSetId ) ;
313+ const fsName = featureSet ?. name || 'Feature set' ;
314+
312315 try {
313316 if ( grantedFeatureSetIds . includes ( featureSetId ) ) {
314317 await revokeOAuthClientFeatureSet ( selectedClient . client_id , activeSpace . id , featureSetId ) ;
315318 setGrantedFeatureSetIds ( prev => prev . filter ( id => id !== featureSetId ) ) ;
319+ success ( 'Permission revoked' , `"${ fsName } " removed from client` ) ;
316320 } else {
317321 await grantOAuthClientFeatureSet ( selectedClient . client_id , activeSpace . id , featureSetId ) ;
318322 setGrantedFeatureSetIds ( prev => [ ...prev , featureSetId ] ) ;
323+ success ( 'Permission granted' , `"${ fsName } " added to client` ) ;
319324 }
320325 loadResolvedFeatures ( selectedClient . client_id ) ;
321326 } catch ( e ) {
322- setError ( e instanceof Error ? e . message : String ( e ) ) ;
327+ const msg = e instanceof Error ? e . message : String ( e ) ;
328+ setError ( msg ) ;
329+ showError ( 'Failed to update permission' , msg ) ;
323330 }
324331 } ;
325332
@@ -345,8 +352,11 @@ export default function ClientsPage() {
345352 ) ) ;
346353
347354 setSelectedClient ( updated ) ;
355+ success ( 'Client settings saved' , `"${ updated . client_alias || updated . client_name } " has been updated` ) ;
348356 } catch ( e ) {
349- setError ( e instanceof Error ? e . message : String ( e ) ) ;
357+ const msg = e instanceof Error ? e . message : String ( e ) ;
358+ setError ( msg ) ;
359+ showError ( 'Failed to save settings' , msg ) ;
350360 } finally {
351361 setIsSaving ( false ) ;
352362 }
@@ -355,12 +365,18 @@ export default function ClientsPage() {
355365 const handleDelete = async ( clientId : string ) => {
356366 if ( ! confirm ( 'Remove this client? All tokens will be revoked.' ) ) return ;
357367
368+ const deletedClient = oauthClients . find ( c => c . client_id === clientId ) ;
369+ const clientName = deletedClient ?. client_alias || deletedClient ?. client_name || 'Client' ;
370+
358371 try {
359372 await deleteOAuthClient ( clientId ) ;
360373 setOAuthClients ( prev => prev . filter ( c => c . client_id !== clientId ) ) ;
361374 setSelectedClient ( null ) ;
375+ success ( 'Client removed' , `"${ clientName } " and its tokens have been revoked` ) ;
362376 } catch ( e ) {
363- setError ( e instanceof Error ? e . message : String ( e ) ) ;
377+ const msg = e instanceof Error ? e . message : String ( e ) ;
378+ setError ( msg ) ;
379+ showError ( 'Failed to remove client' , msg ) ;
364380 }
365381 } ;
366382
@@ -440,13 +456,15 @@ export default function ClientsPage() {
440456 const isAdded = individualFeatureMembers . some ( m => m . member_id === featureId ) ;
441457 console . log ( 'Feature is currently added:' , isAdded ) ;
442458
459+ const feature = availableFeatures . find ( f => f . id === featureId ) ;
460+ const featureName = feature ?. feature_name || 'Feature' ;
461+
443462 try {
444463 if ( isAdded ) {
445- console . log ( 'Removing feature from set...' ) ;
446464 await removeFeatureFromSet ( clientCustomFeatureSet . id , featureId ) ;
447465 setIndividualFeatureMembers ( prev => prev . filter ( m => m . member_id !== featureId ) ) ;
466+ success ( 'Feature removed' , `"${ featureName } " removed from client` ) ;
448467 } else {
449- console . log ( 'Adding feature to set...' ) ;
450468 await addFeatureToSet ( clientCustomFeatureSet . id , featureId , 'include' ) ;
451469 setIndividualFeatureMembers ( prev => [ ...prev , {
452470 id : '' ,
@@ -455,14 +473,14 @@ export default function ClientsPage() {
455473 member_id : featureId ,
456474 mode : 'include' ,
457475 } ] ) ;
476+ success ( 'Feature added' , `"${ featureName } " added to client` ) ;
458477 }
459478
460- console . log ( 'Reloading resolved features...' ) ;
461479 await loadResolvedFeatures ( selectedClient . client_id ) ;
462- console . log ( 'Toggle complete' ) ;
463480 } catch ( e ) {
464- console . error ( 'Error toggling feature:' , e ) ;
465- setError ( e instanceof Error ? e . message : String ( e ) ) ;
481+ const msg = e instanceof Error ? e . message : String ( e ) ;
482+ setError ( msg ) ;
483+ showError ( 'Failed to toggle feature' , msg ) ;
466484 }
467485 } ;
468486
@@ -1282,21 +1300,7 @@ export default function ClientsPage() {
12821300 </ div >
12831301 ) }
12841302
1285- { /* Toast notification */ }
1286- { toast && (
1287- < div className = "fixed bottom-4 right-4 z-50 animate-in slide-in-from-bottom-4" >
1288- < div className = { `px-4 py-2 rounded-lg shadow-lg ${
1289- toast . type === 'success'
1290- ? 'bg-green-600 text-white'
1291- : 'bg-[rgb(var(--surface-elevated))] border border-[rgb(var(--border))] text-[rgb(var(--foreground))]'
1292- } `} >
1293- < div className = "flex items-center gap-2" >
1294- < Wifi className = "h-4 w-4" />
1295- < span className = "text-sm" > { toast . message } </ span >
1296- </ div >
1297- </ div >
1298- </ div >
1299- ) }
1303+ < ToastContainer toasts = { toasts } onClose = { dismiss } />
13001304 </ div >
13011305 ) ;
13021306}
0 commit comments