11import React , { useState , useEffect } from 'react' ;
2- import { Users , Database , Shield , Download , Upload , Settings2 , RefreshCw , AlertCircle , Lock , Key , Globe , CheckCircle , XCircle , AlertTriangle , FileText , Calendar , Server , Network } from 'lucide-react' ;
2+ import { Users , Database , Shield , Download , Upload , Settings2 , RefreshCw , AlertCircle , Lock , Key , Globe , CheckCircle , XCircle , AlertTriangle , FileText , Calendar , Server , Network , Copy , Eye , EyeOff } from 'lucide-react' ;
33import { useAuth } from '../contexts/AuthContext' ;
44import { AdminUserManagement } from '../components/AdminUserManagement' ;
55import { CustomDropdown } from '../components/CustomDropdown' ;
@@ -26,6 +26,7 @@ export function Admin() {
2626 const tabs = [
2727 { id : 'users' , name : 'Users' , icon : Users , description : 'Manage user roles and permissions' } ,
2828 { id : 'settings' , name : 'Registration' , icon : Settings2 , description : 'User registration policies and defaults' } ,
29+ { id : 'oauth' , name : 'OAuth Providers' , icon : Key , description : 'Configure OAuth authentication providers' } ,
2930 { id : 'database' , name : 'Database' , icon : Database , description : 'Database configuration and maintenance' } ,
3031 { id : 'security' , name : 'Security' , icon : Shield , description : 'Security settings and passwords' } ,
3132 { id : 'backup' , name : 'Backup & Restore' , icon : Download , description : 'System backup and restore' } ,
@@ -37,6 +38,8 @@ export function Admin() {
3738 return < AdminUserManagement /> ;
3839 case 'settings' :
3940 return < UserManagementSettings /> ;
41+ case 'oauth' :
42+ return < OAuthProviderManagement /> ;
4043 case 'database' :
4144 return < DatabaseManagement /> ;
4245 case 'security' :
@@ -262,6 +265,251 @@ function UserManagementSettings() {
262265 ) ;
263266}
264267
268+ // OAuth Provider Management Component
269+ function OAuthProviderManagement ( ) {
270+ const [ providers , setProviders ] = useState ( {
271+ google : {
272+ enabled : false ,
273+ clientId : '' ,
274+ clientSecret : '' ,
275+ callbackUrl : 'https://localhost:4128/auth/google/callback' ,
276+ configured : false ,
277+ } ,
278+ linkedin : {
279+ enabled : false ,
280+ clientId : '' ,
281+ clientSecret : '' ,
282+ callbackUrl : 'https://localhost:4128/auth/linkedin/callback' ,
283+ configured : false ,
284+ } ,
285+ github : {
286+ enabled : false ,
287+ clientId : '' ,
288+ clientSecret : '' ,
289+ callbackUrl : 'https://localhost:4128/auth/github/callback' ,
290+ configured : false ,
291+ } ,
292+ } ) ;
293+
294+ const [ showSecrets , setShowSecrets ] = useState ( {
295+ google : false ,
296+ linkedin : false ,
297+ github : false ,
298+ } ) ;
299+
300+ const [ saved , setSaved ] = useState ( false ) ;
301+ const [ loading , setLoading ] = useState ( false ) ;
302+
303+ useEffect ( ( ) => {
304+ // TODO: Load OAuth provider configuration from backend
305+ console . log ( 'Loading OAuth provider configuration...' ) ;
306+ } , [ ] ) ;
307+
308+ const handleSave = async ( ) => {
309+ setLoading ( true ) ;
310+ try {
311+ // TODO: Save OAuth provider configuration to backend
312+ console . log ( 'Saving OAuth provider configuration:' , providers ) ;
313+
314+ // Simulate API call
315+ await new Promise ( resolve => setTimeout ( resolve , 1000 ) ) ;
316+
317+ setSaved ( true ) ;
318+ setTimeout ( ( ) => setSaved ( false ) , 3000 ) ;
319+ } catch ( error ) {
320+ console . error ( 'Failed to save OAuth configuration:' , error ) ;
321+ } finally {
322+ setLoading ( false ) ;
323+ }
324+ } ;
325+
326+ const handleCopyCallback = ( url : string ) => {
327+ navigator . clipboard . writeText ( url ) ;
328+ console . log ( 'Copied callback URL:' , url ) ;
329+ } ;
330+
331+ const toggleShowSecret = ( provider : 'google' | 'linkedin' | 'github' ) => {
332+ setShowSecrets ( prev => ( { ...prev , [ provider ] : ! prev [ provider ] } ) ) ;
333+ } ;
334+
335+ const updateProvider = ( provider : 'google' | 'linkedin' | 'github' , field : string , value : any ) => {
336+ setProviders ( prev => ( {
337+ ...prev ,
338+ [ provider ] : {
339+ ...prev [ provider ] ,
340+ [ field ] : value ,
341+ configured : field === 'clientId' || field === 'clientSecret'
342+ ? ( value && prev [ provider ] . clientId && prev [ provider ] . clientSecret )
343+ : prev [ provider ] . configured ,
344+ } ,
345+ } ) ) ;
346+ } ;
347+
348+ const renderProviderConfig = (
349+ providerKey : 'google' | 'linkedin' | 'github' ,
350+ providerName : string ,
351+ providerIcon : React . ReactNode
352+ ) => {
353+ const provider = providers [ providerKey ] ;
354+ const showSecret = showSecrets [ providerKey ] ;
355+
356+ return (
357+ < div key = { providerKey } className = "bg-gray-800 border border-gray-700 rounded-lg p-6" >
358+ < div className = "flex items-center justify-between mb-6" >
359+ < div className = "flex items-center space-x-3" >
360+ { providerIcon }
361+ < div >
362+ < h3 className = "text-lg font-semibold text-gray-100" > { providerName } </ h3 >
363+ < p className = "text-sm text-gray-400" >
364+ { provider . configured
365+ ? < span className = "flex items-center text-green-400" > < CheckCircle className = "h-4 w-4 mr-1" /> Configured</ span >
366+ : < span className = "flex items-center text-gray-500" > < AlertCircle className = "h-4 w-4 mr-1" /> Not configured</ span >
367+ }
368+ </ p >
369+ </ div >
370+ </ div >
371+ < div className = "flex items-center space-x-2" >
372+ < span className = "text-sm text-gray-400" > Enable</ span >
373+ < input
374+ type = "checkbox"
375+ checked = { provider . enabled }
376+ onChange = { ( e ) => updateProvider ( providerKey , 'enabled' , e . target . checked ) }
377+ className = "h-4 w-4 text-green-500 focus:ring-green-500 border-gray-500 bg-gray-700 rounded"
378+ />
379+ </ div >
380+ </ div >
381+
382+ < div className = "space-y-4" >
383+ < div >
384+ < label className = "block text-sm font-medium text-gray-300 mb-2" >
385+ Client ID
386+ </ label >
387+ < input
388+ type = "text"
389+ value = { provider . clientId }
390+ onChange = { ( e ) => updateProvider ( providerKey , 'clientId' , e . target . value ) }
391+ placeholder = { `Enter ${ providerName } Client ID` }
392+ className = "w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
393+ />
394+ </ div >
395+
396+ < div >
397+ < label className = "block text-sm font-medium text-gray-300 mb-2" >
398+ Client Secret
399+ </ label >
400+ < div className = "relative" >
401+ < input
402+ type = { showSecret ? 'text' : 'password' }
403+ value = { provider . clientSecret }
404+ onChange = { ( e ) => updateProvider ( providerKey , 'clientSecret' , e . target . value ) }
405+ placeholder = { `Enter ${ providerName } Client Secret` }
406+ className = "w-full px-3 py-2 pr-10 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
407+ />
408+ < button
409+ type = "button"
410+ onClick = { ( ) => toggleShowSecret ( providerKey ) }
411+ className = "absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-300"
412+ >
413+ { showSecret ? < EyeOff className = "h-4 w-4" /> : < Eye className = "h-4 w-4" /> }
414+ </ button >
415+ </ div >
416+ </ div >
417+
418+ < div >
419+ < label className = "block text-sm font-medium text-gray-300 mb-2" >
420+ Callback URL
421+ < span className = "text-gray-500 text-xs ml-2" > (Copy this to your OAuth app configuration)</ span >
422+ </ label >
423+ < div className = "flex items-center space-x-2" >
424+ < input
425+ type = "text"
426+ value = { provider . callbackUrl }
427+ readOnly
428+ className = "flex-1 px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-300 cursor-not-allowed"
429+ />
430+ < button
431+ type = "button"
432+ onClick = { ( ) => handleCopyCallback ( provider . callbackUrl ) }
433+ className = "px-3 py-2 bg-gray-700 hover:bg-gray-600 border border-gray-600 rounded-lg text-gray-300 transition-colors"
434+ title = "Copy callback URL"
435+ >
436+ < Copy className = "h-4 w-4" />
437+ </ button >
438+ </ div >
439+ </ div >
440+ </ div >
441+ </ div >
442+ ) ;
443+ } ;
444+
445+ return (
446+ < div className = "max-w-5xl mx-auto px-6 py-8" >
447+ < div className = "mb-6" >
448+ < h2 className = "text-2xl font-bold text-gray-100 mb-2" > OAuth Provider Configuration</ h2 >
449+ < p className = "text-gray-400" >
450+ Configure OAuth authentication providers for user sign-in. Users can login using their existing accounts from these providers.
451+ </ p >
452+ </ div >
453+
454+ < div className = "space-y-6" >
455+ { renderProviderConfig ( 'google' , 'Google' , < Globe className = "h-6 w-6 text-red-400" /> ) }
456+ { renderProviderConfig ( 'linkedin' , 'LinkedIn' , < Network className = "h-6 w-6 text-blue-400" /> ) }
457+ { renderProviderConfig ( 'github' , 'GitHub' , < Server className = "h-6 w-6 text-purple-400" /> ) }
458+ </ div >
459+
460+ < div className = "mt-8 flex items-center justify-between p-4 bg-gray-800/50 border border-gray-700 rounded-lg" >
461+ < div className = "text-sm text-gray-400" >
462+ { saved && (
463+ < span className = "flex items-center text-green-400" >
464+ < CheckCircle className = "h-4 w-4 mr-2" />
465+ OAuth configuration saved successfully
466+ </ span >
467+ ) }
468+ </ div >
469+ < div className = "flex space-x-3" >
470+ < button
471+ onClick = { ( ) => window . location . reload ( ) }
472+ className = "px-4 py-2 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded-lg transition-colors"
473+ disabled = { loading }
474+ >
475+ Reset
476+ </ button >
477+ < button
478+ onClick = { handleSave }
479+ disabled = { loading }
480+ className = "px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center"
481+ >
482+ { loading ? (
483+ < >
484+ < RefreshCw className = "h-4 w-4 mr-2 animate-spin" />
485+ Saving...
486+ </ >
487+ ) : (
488+ 'Save OAuth Configuration'
489+ ) }
490+ </ button >
491+ </ div >
492+ </ div >
493+
494+ < div className = "mt-6 p-4 bg-yellow-900/20 border border-yellow-600/30 rounded-lg" >
495+ < div className = "flex items-start space-x-3" >
496+ < AlertTriangle className = "h-5 w-5 text-yellow-400 flex-shrink-0 mt-0.5" />
497+ < div className = "text-sm text-yellow-200/90" >
498+ < p className = "font-medium mb-1" > Setup Instructions:</ p >
499+ < ol className = "list-decimal list-inside space-y-1 text-yellow-200/70" >
500+ < li > Create an OAuth app in the provider's developer console</ li >
501+ < li > Copy the Client ID and Client Secret</ li >
502+ < li > Add the Callback URL to your OAuth app's allowed redirect URIs</ li >
503+ < li > Paste the credentials here and enable the provider</ li >
504+ < li > Save the configuration</ li >
505+ </ ol >
506+ </ div >
507+ </ div >
508+ </ div >
509+ </ div >
510+ ) ;
511+ }
512+
265513// Database Management Component with full admin tools
266514function DatabaseManagement ( ) {
267515 const [ debugInfo , setDebugInfo ] = useState < string [ ] > ( [ ] ) ;
0 commit comments