11/* eslint-disable max-len */
22'use client' ;
3- import React from 'react ' ;
4- import ApiService from '#/services/frontend/ api.service ' ;
3+ import LoadingState from '#/components/Loading ' ;
4+ import { objectToQueryString } from '#/lib/ api-caller ' ;
55import { ROUTING } from '#/lib/router' ;
6+ import ApiService from '#/services/frontend/api.service' ;
67import type { AuthRequest , Session } from '#/types/zitadel' ;
78import useTranslations from 'next-translate/useTranslation' ;
8- import Image from 'next/image' ;
99import { useRouter } from 'next/navigation' ;
10- import { objectToQueryString } from '#/lib/api-caller ' ;
10+ import React , { useState } from 'react ' ;
1111
1212export default ( props : {
1313 appUrl : string ;
@@ -18,38 +18,43 @@ export default (props: {
1818 const router = useRouter ( ) ;
1919 const { t } = useTranslations ( 'account_select' ) ;
2020 const apiService = ApiService ( { appUrl } ) ;
21+ const [ pending , setPending ] = useState < string | null > ( null ) ;
22+ const isLoading = pending !== null ;
2123
22- const formatDisplayText = ( text ?: string , length = 25 ) => {
24+ const formatDisplayText = ( text ?: string , length = 32 ) => {
2325 if ( ! text ) return t ( 'empty' ) ;
24- return text ? .length ? text . substring ( 0 , length ) : text ;
26+ return text . length > length ? ` ${ text . substring ( 0 , length ) } …` : text ;
2527 } ;
2628
29+ const initialOf = ( name ?: string ) =>
30+ name && name . length ? name . substring ( 0 , 1 ) . toUpperCase ( ) : 'A' ;
31+
32+ const activeSessions = sessions . filter ( ( e ) => ! ! e . factors ?. user ?. id ) ;
33+
2734 return (
28- < div className = "flex flex-col bg-white w-screen h-screen" >
29- < div className = "min-h-screen bg-gray-100 flex justify-center items-center" >
30- < div className = "bg-white w-[416px] py-4 rounded-md shadow-md" >
31- < div className = "flex flex-col items-center justify-center text-center p-2" >
32- < Image
33- src = "/images/company.png"
34- alt = "logo"
35- width = { 100 }
36- height = "75"
37- />
38- < div >
39- < h2 className = "text-lg font-semibold" >
40- { t ( 'accountSelectTitle' ) }
41- </ h2 >
42- </ div >
43- </ div >
35+ < div className = "flex h-full w-full flex-col items-center justify-center bg-gray-50 px-4 py-8 sm:py-12" >
36+ < LoadingState loading = { isLoading } />
4437
45- < div >
46- { [ ...sessions ]
47- . filter ( ( e ) => ! ! e . factors ?. user ?. id )
48- . map ( ( session ) => (
49- < div
50- key = { session . id }
51- className = "flex items-center p-2 mb-4 hover:bg-gray-100 hover:cursor-pointer"
38+ < div className = "w-full max-w-[440px] overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm" >
39+ < div className = "px-6 pb-2 pt-6 sm:px-8 sm:pt-8" >
40+ < h1 className = "text-center text-2xl font-bold text-gray-900 sm:text-3xl" >
41+ { t ( 'accountSelectTitle' ) }
42+ </ h1 >
43+ </ div >
44+
45+ < ul className = "divide-y divide-gray-100" role = "list" >
46+ { activeSessions . map ( ( session ) => {
47+ const displayName = session . factors ?. user ?. displayName ;
48+ const loginName = session . factors ?. user ?. loginName ;
49+
50+ return (
51+ < li key = { session . id } >
52+ < button
53+ type = "button"
54+ disabled = { isLoading }
55+ className = "flex w-full items-center gap-3 px-6 py-3 text-left transition-colors hover:bg-gray-50 focus:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-60 sm:px-8"
5256 onClick = { async ( ) => {
57+ setPending ( session . id ) ;
5358 try {
5459 if ( authRequest && session ?. factors ?. user ?. id ) {
5560 const result = await apiService . finalizeAuthRequest ( {
@@ -69,42 +74,97 @@ export default (props: {
6974 router . push ( `${ ROUTING . ACCOUNT } /${ index } ` ) ;
7075 } catch ( error ) {
7176 console . error ( error ) ;
77+ setPending ( null ) ;
7278 }
7379 } }
7480 >
75- < div className = "flex items-center justify-center h-10 w-10 rounded-full mr-4 ring-2" >
76- < div className = "text-center" >
77- { session
78- ? session . factors ?. user ?. displayName ?. substring ( 0 , 1 )
79- : 'A' }
80- </ div >
81- </ div >
81+ < span
82+ aria-hidden = "true"
83+ className = "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-indigo-100 text-sm font-semibold text-indigo-700"
84+ >
85+ { initialOf ( displayName || loginName ) }
86+ </ span >
8287
83- < div >
84- < h3 className = "text-lg font-semibold" >
85- { formatDisplayText ( session . factors ?. user ?. displayName ) }
86- </ h3 >
87- < p className = "text-gray-600" >
88- { formatDisplayText ( session . factors ?. user ?. loginName ) }
89- </ p >
90- </ div >
91- </ div >
92- ) ) }
88+ < span className = "flex min-w-0 flex-1 flex-col" >
89+ < span className = "truncate text-sm font-medium text-gray-900" >
90+ { formatDisplayText ( displayName ) }
91+ </ span >
92+ < span className = "truncate text-xs text-gray-500" >
93+ { formatDisplayText ( loginName ) }
94+ </ span >
95+ </ span >
9396
94- < div
95- className = "flex items-center p-4 hover:bg-gray-100 hover:cursor-pointer"
97+ { pending === session . id ? (
98+ < span
99+ aria-hidden = "true"
100+ className = "h-4 w-4 flex-shrink-0 animate-spin rounded-full border-2 border-gray-300 border-t-indigo-600"
101+ />
102+ ) : (
103+ < svg
104+ aria-hidden = "true"
105+ width = "16"
106+ height = "16"
107+ viewBox = "0 0 24 24"
108+ fill = "none"
109+ stroke = "currentColor"
110+ strokeWidth = "2"
111+ strokeLinecap = "round"
112+ strokeLinejoin = "round"
113+ className = "flex-shrink-0 text-gray-400"
114+ >
115+ < polyline points = "9 18 15 12 9 6" />
116+ </ svg >
117+ ) }
118+ </ button >
119+ </ li >
120+ ) ;
121+ } ) }
122+
123+ < li >
124+ < button
125+ type = "button"
126+ disabled = { isLoading }
127+ className = "flex w-full items-center gap-3 px-6 py-3 text-left transition-colors hover:bg-gray-50 focus:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-60 sm:px-8"
96128 onClick = { ( ) => {
129+ setPending ( 'add' ) ;
97130 router . push (
98131 objectToQueryString ( ROUTING . LOGIN , {
99132 authRequest : authRequest ?. id ,
100133 } ) ,
101134 ) ;
102135 } }
103136 >
104- < p > { t ( 'addAccount' ) } </ p >
105- </ div >
106- </ div >
107- </ div >
137+ < span
138+ aria-hidden = "true"
139+ className = "flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full border border-dashed border-gray-300 text-gray-500"
140+ >
141+ { pending === 'add' ? (
142+ < span
143+ aria-hidden = "true"
144+ className = "h-4 w-4 animate-spin rounded-full border-2 border-gray-300 border-t-indigo-600"
145+ />
146+ ) : (
147+ < svg
148+ width = "18"
149+ height = "18"
150+ viewBox = "0 0 24 24"
151+ fill = "none"
152+ stroke = "currentColor"
153+ strokeWidth = "2"
154+ strokeLinecap = "round"
155+ strokeLinejoin = "round"
156+ >
157+ < line x1 = "12" y1 = "5" x2 = "12" y2 = "19" />
158+ < line x1 = "5" y1 = "12" x2 = "19" y2 = "12" />
159+ </ svg >
160+ ) }
161+ </ span >
162+ < span className = "text-sm font-medium text-gray-700" >
163+ { t ( 'addAccount' ) }
164+ </ span >
165+ </ button >
166+ </ li >
167+ </ ul >
108168 </ div >
109169 </ div >
110170 ) ;
0 commit comments