@@ -16,6 +16,7 @@ import { SuggestionStats } from '../components/SuggestionStats';
1616import { SuggestionCard } from '../components/SuggestionCard' ;
1717import { SuggestionPreviewModal } from '../components/SuggestionPreviewModal' ;
1818import { VirtualizedSuggestionList } from '../components/VirtualizedSuggestionList' ;
19+ import { useRoleGate } from '../hooks/useRoleGate' ;
1920
2021const VIRTUALIZATION_THRESHOLD = 50 ;
2122
@@ -100,6 +101,8 @@ export const SuggestionDashboard: React.FC = () => {
100101 const cardRefs = useRef < ( HTMLDivElement | null ) [ ] > ( [ ] ) ;
101102 const [ previewSuggestion , setPreviewSuggestion ] = useState < Suggestion | null > ( null ) ;
102103 const { showToast } = useToast ( ) ;
104+ const { isAdmin, hasRole } = useRoleGate ( ) ;
105+ const canReview = hasRole ( [ 'admin' , 'reviewer' ] ) ;
103106
104107 // Filters state: priority URL params > localStorage > defaults
105108 const [ filters , setFilters ] = useState < Filters > ( ( ) =>
@@ -273,6 +276,7 @@ export const SuggestionDashboard: React.FC = () => {
273276 setFocusedIndex ( ( prev ) => ( prev > 0 ? prev - 1 : prev ) ) ;
274277 break ;
275278 case 'a' : {
279+ if ( ! canReview ) break ;
276280 const hasFocusedCard = selectedIds . size === 0 && focusedIndex >= 0 ;
277281 if ( hasFocusedCard ) {
278282 // Accept focused card
@@ -284,6 +288,7 @@ export const SuggestionDashboard: React.FC = () => {
284288 break ;
285289 }
286290 case 'r' : {
291+ if ( ! canReview ) break ;
287292 const hasFocusedCard = selectedIds . size === 0 && focusedIndex >= 0 ;
288293 if ( hasFocusedCard ) {
289294 handleReview ( suggestions [ focusedIndex ] . id , SuggestionStatus . REJECTED ) ;
@@ -297,6 +302,7 @@ export const SuggestionDashboard: React.FC = () => {
297302 case ' ' :
298303 if ( focusedIndex >= 0 ) {
299304 e . preventDefault ( ) ;
305+ if ( ! canReview ) break ;
300306 const focusedId = suggestions [ focusedIndex ] . id ;
301307 setSelectedIds ( ( prev ) => {
302308 const next = new Set ( prev ) ;
@@ -313,15 +319,15 @@ export const SuggestionDashboard: React.FC = () => {
313319 if ( focusedIndex >= 0 && selectedIds . size === 0 ) {
314320 // Open preview for focused suggestion
315321 setPreviewSuggestion ( suggestions [ focusedIndex ] ) ;
316- } else if ( selectedIds . size > 0 ) {
322+ } else if ( selectedIds . size > 0 && canReview ) {
317323 // Accept selected
318324 handleBulkAccept ( ) ;
319325 }
320326 break ;
321327 case 'Delete' :
322328 case 'Backspace' :
323329 // Reject selected
324- if ( selectedIds . size > 0 && e . shiftKey ) {
330+ if ( selectedIds . size > 0 && e . shiftKey && canReview ) {
325331 e . preventDefault ( ) ;
326332 handleBulkReject ( ) ;
327333 }
@@ -331,7 +337,7 @@ export const SuggestionDashboard: React.FC = () => {
331337
332338 window . addEventListener ( 'keydown' , handleKeyPress ) ;
333339 return ( ) => window . removeEventListener ( 'keydown' , handleKeyPress ) ;
334- } , [ suggestions , selectedIds , focusedIndex , handleBulkAccept , handleBulkReject , handleReview ] ) ;
340+ } , [ suggestions , selectedIds , focusedIndex , canReview , handleBulkAccept , handleBulkReject , handleReview ] ) ;
335341
336342 const handleToggleSelect = useCallback ( ( id : string , checked : boolean ) => {
337343 setSelectedIds ( ( prev ) => {
@@ -357,27 +363,33 @@ export const SuggestionDashboard: React.FC = () => {
357363 < div className = "container mx-auto px-4 py-8" >
358364 < div className = "flex items-center justify-between mb-8" >
359365 < h1 className = "text-3xl font-bold text-gray-900" > AI Suggestion Dashboard</ h1 >
360- < button
361- onClick = { handleGenerate }
362- disabled = { generating }
363- className = "px-6 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center gap-2"
364- >
365- { generating ? (
366- < >
367- < LoadingSpinner size = "sm" />
368- Generating...
369- </ >
370- ) : (
371- 'Generate Suggestions'
366+ < div className = "flex items-center gap-3" >
367+ { isAdmin && (
368+ < button
369+ onClick = { handleGenerate }
370+ disabled = { generating }
371+ className = "px-6 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center gap-2"
372+ >
373+ { generating ? (
374+ < >
375+ < LoadingSpinner size = "sm" />
376+ Generating...
377+ </ >
378+ ) : (
379+ 'Generate Suggestions'
380+ ) }
381+ </ button >
382+ ) }
383+ { canReview && (
384+ < button
385+ onClick = { handleExportCsv }
386+ disabled = { exporting }
387+ className = "px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400"
388+ >
389+ Export CSV
390+ </ button >
372391 ) }
373- </ button >
374- < button
375- onClick = { handleExportCsv }
376- disabled = { exporting }
377- className = "px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400"
378- >
379- Export CSV
380- </ button >
392+ </ div >
381393 </ div >
382394
383395 < KeyboardShortcutsHelp />
@@ -425,7 +437,7 @@ export const SuggestionDashboard: React.FC = () => {
425437 ) }
426438
427439 { /* Bulk action bar */ }
428- { selectedIds . size > 0 && (
440+ { selectedIds . size > 0 && canReview && (
429441 < div className = "fixed bottom-0 left-0 right-0 bg-primary-600 text-white shadow-lg p-4 flex items-center justify-between z-50" >
430442 < div >
431443 < span className = "font-semibold" > { selectedIds . size } suggestions selected</ span >
0 commit comments