@@ -157,6 +157,16 @@ function getProviderIcon(name: string): string {
157157 return icons [name ] || ' 🔑'
158158}
159159
160+ // Copy text to clipboard
161+ async function copyToClipboard(text : string ) {
162+ try {
163+ await navigator .clipboard .writeText (text )
164+ console .log (' Error message copied to clipboard' )
165+ } catch (err ) {
166+ console .error (' Failed to copy text to clipboard:' , err )
167+ }
168+ }
169+
160170const clearActiveTab = () => {
161171 const activeLinks = document .querySelectorAll <HTMLElement >(' .router-link' )
162172 for (const active of activeLinks ) {
@@ -313,28 +323,84 @@ const getCurrentPath = () => {
313323 <!-- Provider Selection Dialog -->
314324 <el-dialog
315325 v-model =" showProviderSelector"
316- title =" Select Identity Provider "
317- width =" 450px "
326+ title =" Login "
327+ width =" 500px "
318328 :close-on-click-modal =" true"
319329 >
320- <div class =" provider-list" >
321- <div
322- v-for =" provider in availableProviders.filter(p => p.available)"
323- :key =" provider.name"
324- class =" provider-item"
325- @click =" loginWithProvider(provider.name); showProviderSelector = false"
326- >
327- <div class =" provider-icon" >{{ getProviderIcon(provider.name) }}</div >
328- <div class =" provider-info" >
329- <h4 >{{ formatProviderName(provider.name) }}</h4 >
330- <span class =" provider-status" >Available</span >
330+ <!-- No providers available -->
331+ <div v-if =" availableProviders.filter(p => p.available).length === 0" class =" no-providers-error" >
332+ <p class =" error-message" >No authentication providers available.</p >
333+ <p class =" error-hint" >Please contact your administrator.</p >
334+
335+ <!-- Show unavailable providers even when no available providers -->
336+ <div v-if =" availableProviders.filter(p => !p.available).length > 0" class =" unavailable-section" >
337+ <p class =" unavailable-header" >Currently unavailable:</p >
338+ <div
339+ v-for =" provider in availableProviders.filter(p => !p.available)"
340+ :key =" provider.name"
341+ class =" provider-unavailable"
342+ >
343+ <div class =" provider-unavailable-header" >
344+ <span class =" provider-status-indicator offline" >●</span >
345+ <span class =" provider-name" >{{ formatProviderName(provider.name) }}</span >
346+ <span class =" unavailable-label" >Unavailable</span >
347+ </div >
348+ <div v-if =" provider.error" class =" provider-error" >
349+ <div class =" provider-error-text" >{{ provider.error }}</div >
350+ <button
351+ @click.stop =" copyToClipboard(provider.error)"
352+ class =" copy-button"
353+ title =" Copy error message"
354+ >
355+ 📋
356+ </button >
357+ </div >
331358 </div >
332- <div class =" provider-arrow" >→</div >
333359 </div >
360+ </div >
334361
335- <div v-if =" availableProviders.filter(p => p.available).length === 0" class =" no-providers" >
336- <p >No identity providers available</p >
337- <p class =" error-hint" >Please contact your administrator</p >
362+ <!-- Available providers -->
363+ <div v-else class =" provider-selection" >
364+ <p class =" selection-hint" >Choose your authentication provider:</p >
365+
366+ <div class =" available-providers" >
367+ <button
368+ v-for =" provider in availableProviders.filter(p => p.available)"
369+ :key =" provider.name"
370+ class =" provider-button"
371+ @click =" loginWithProvider(provider.name); showProviderSelector = false"
372+ >
373+ <span class =" provider-button-content" >
374+ <span class =" provider-status-indicator online" >●</span >
375+ <span class =" provider-button-text" >{{ formatProviderName(provider.name) }}</span >
376+ </span >
377+ </button >
378+ </div >
379+
380+ <!-- Unavailable providers section -->
381+ <div v-if =" availableProviders.filter(p => !p.available).length > 0" class =" unavailable-section" >
382+ <p class =" unavailable-header" >Currently unavailable:</p >
383+ <div
384+ v-for =" provider in availableProviders.filter(p => !p.available)"
385+ :key =" provider.name"
386+ class =" provider-unavailable"
387+ >
388+ <div class =" provider-unavailable-header" >
389+ <span class =" provider-status-indicator offline" >●</span >
390+ <span class =" provider-name" >{{ formatProviderName(provider.name) }}</span >
391+ <span class =" unavailable-label" >Unavailable</span >
392+ </div >
393+ <div v-if =" provider.error" class =" provider-error" >
394+ <div class =" provider-error-text" >{{ provider.error }}</div >
395+ <button
396+ @click.stop =" copyToClipboard(provider.error)"
397+ class =" copy-button"
398+ title =" Copy error message"
399+ >
400+ 📋
401+ </button >
402+ </div >
403+ </div >
338404 </div >
339405 </div >
340406 </el-dialog >
@@ -435,71 +501,165 @@ button.login-button-disabled {
435501}
436502
437503/* Provider Selection Dialog */
438- .provider-list {
504+ .provider-selection {
439505 display : flex ;
440506 flex-direction : column ;
441- gap : 12px ;
507+ gap : 16px ;
508+ }
509+
510+ .selection-hint {
511+ text-align : center ;
512+ font-size : 14px ;
513+ color : #666 ;
514+ margin : 0 0 8px 0 ;
442515}
443516
444- .provider-item {
517+ .available-providers {
445518 display : flex ;
446- align-items : center ;
447- padding : 16px ;
448- border : 2px solid #e0e0e0 ;
519+ flex-direction : column ;
520+ gap : 12px ;
521+ }
522+
523+ .provider-button {
524+ width : 100% ;
525+ padding : 14px 20px ;
526+ background-color : #32b9ce ;
527+ color : white ;
528+ border : none ;
449529 border-radius : 8px ;
530+ font-size : 15px ;
531+ font-family : ' Roboto' , sans-serif ;
450532 cursor : pointer ;
451- transition : all 0.2s ;
452- background-color : #ffffff ;
533+ transition : all 0.2s ease ;
534+ font-weight : 500 ;
453535}
454536
455- .provider-item :hover {
456- border -color : #32b9ce ;
457- background-color : #f0f9fa ;
458- transform : translateX ( 4px );
537+ .provider-button :hover {
538+ background -color : #2a9fb0 ;
539+ transform : translateY ( -1 px ) ;
540+ box-shadow : 0 4px 8 px rgba ( 50 , 185 , 206 , 0.3 );
459541}
460542
461- .provider-icon {
462- font-size : 32 px ;
463- margin-right : 16 px ;
464- min-width : 40 px ;
465- text-align : center ;
543+ .provider-button-content {
544+ display : flex ;
545+ align-items : center ;
546+ justify-content : space-between ;
547+ width : 100 % ;
466548}
467549
468- .provider-info {
550+ .provider-button-text {
469551 flex : 1 ;
552+ text-align : left ;
553+ margin-left : 8px ;
470554}
471555
472- .provider-info h4 {
473- margin : 0 0 4px 0 ;
474- font-size : 16px ;
475- color : #39455f ;
476- font-weight : 500 ;
556+ .provider-status-indicator {
557+ font-size : 14px ;
558+ margin-right : 8px ;
477559}
478560
479- .provider-status {
480- font-size : 12px ;
561+ .provider-status-indicator.online {
481562 color : #10b981 ;
563+ }
564+
565+ .provider-status-indicator.offline {
566+ color : #ef4444 ;
567+ }
568+
569+ /* Unavailable providers section */
570+ .unavailable-section {
571+ margin-top : 24px ;
572+ padding-top : 20px ;
573+ border-top : 1px solid #e5e7eb ;
574+ }
575+
576+ .unavailable-header {
577+ text-align : center ;
578+ font-size : 13px ;
579+ color : #9ca3af ;
580+ margin : 0 0 12px 0 ;
581+ }
582+
583+ .provider-unavailable {
584+ width : 100% ;
585+ padding : 12px 16px ;
586+ border-radius : 8px ;
587+ border : 1px solid #d1d5db ;
588+ background-color : #f9fafb ;
589+ opacity : 0.7 ;
590+ margin-bottom : 8px ;
591+ }
592+
593+ .provider-unavailable-header {
594+ display : flex ;
595+ align-items : center ;
596+ justify-content : space-between ;
597+ gap : 8px ;
598+ }
599+
600+ .provider-name {
601+ flex : 1 ;
602+ color : #4b5563 ;
603+ font-size : 14px ;
604+ }
605+
606+ .unavailable-label {
607+ font-size : 11px ;
608+ color : #ef4444 ;
482609 font-weight : 500 ;
610+ text-transform : uppercase ;
611+ }
612+
613+ .provider-error {
614+ display : flex ;
615+ align-items : start ;
616+ gap : 8px ;
617+ margin-top : 8px ;
618+ margin-left : 22px ;
619+ }
620+
621+ .provider-error-text {
622+ flex : 1 ;
623+ font-size : 11px ;
624+ color : #6b7280 ;
625+ max-height : 80px ;
626+ overflow-y : auto ;
627+ word-break : break-word ;
628+ white-space : pre-wrap ;
629+ line-height : 1.4 ;
630+ }
631+
632+ .copy-button {
633+ background : none ;
634+ border : none ;
635+ cursor : pointer ;
636+ font-size : 14px ;
637+ padding : 0 ;
638+ opacity : 0.6 ;
639+ transition : opacity 0.2s ;
640+ flex-shrink : 0 ;
483641}
484642
485- .provider-arrow {
486- font-size : 20px ;
487- color : #32b9ce ;
488- margin-left : 12px ;
643+ .copy-button :hover {
644+ opacity : 1 ;
489645}
490646
491- .no-providers {
647+ /* No providers error state */
648+ .no-providers-error {
492649 text-align : center ;
493- padding : 32px ;
494- color : #999 ;
650+ padding : 24px ;
495651}
496652
497- .no-providers p {
498- margin : 8px 0 ;
653+ .error-message {
654+ color : #ef4444 ;
655+ font-size : 15px ;
656+ margin : 0 0 8px 0 ;
657+ font-weight : 500 ;
499658}
500659
501660.error-hint {
502- font-size : 12px ;
503- color : #999 ;
661+ font-size : 13px ;
662+ color : #6b7280 ;
663+ margin : 0 ;
504664}
505665 </style >
0 commit comments