1010
1111'use client'
1212
13- import { AccessToken , Embedded , HttpStatus } from '@/object-types'
13+ import { ColumnDef , getCoreRowModel , useReactTable } from '@tanstack/react-table'
14+ import { getSession , signOut , useSession } from 'next-auth/react'
15+ import { useTranslations } from 'next-intl'
16+ import { SW360Table } from 'next-sw360'
17+ import React , { ReactNode , useEffect , useMemo , useState } from 'react'
18+ import { Button , Spinner } from 'react-bootstrap'
19+ import { AccessToken , Embedded , ErrorDetails , HttpStatus } from '@/object-types'
1420import MessageService from '@/services/message.service'
1521import { ApiUtils , CommonUtils } from '@/utils/index'
16- import { getSession , signOut } from 'next-auth/react'
17- import { useTranslations } from 'next-intl'
18- import { Table , _ } from 'next-sw360'
19- import React , { ReactNode , useCallback , useEffect , useState , type JSX } from 'react'
20- import { Button } from 'react-bootstrap'
22+
23+ type EmbeddedAccessTokens = Embedded < AccessToken , 'sw360:restApiTokens' >
2124
2225interface Props {
2326 generatedToken : string
2427}
2528
2629const TokensTable = ( { generatedToken } : Props ) : ReactNode => {
2730 const t = useTranslations ( 'default' )
28- const [ tableData , setTableData ] = useState < ( string | JSX . Element ) [ ] [ ] > ( [ ] )
29-
30- const fetchData = useCallback ( async ( url : string ) => {
31- try {
32- const session = await getSession ( )
33- if ( CommonUtils . isNullOrUndefined ( session ) ) return signOut ( )
34- const response = await ApiUtils . GET ( url , session . user . access_token )
35-
36- if ( response . status === HttpStatus . OK ) {
37- const data : Embedded < AccessToken , 'sw360:restApiTokens' > = ( await response . json ( ) ) as Embedded <
38- AccessToken ,
39- 'sw360:restApiTokens'
40- >
31+ const session = useSession ( )
32+ const [ revoked , setRevoked ] = useState ( false )
4133
42- const tableData = Object . values ( data . _embedded [ 'sw360:restApiTokens' ] ) . map ( ( token : AccessToken ) => {
43- const expirationDate = new Date (
44- Date . parse ( token . createdOn + ' +0000' ) +
45- token . numberOfDaysValid * 24 * 60 * 60 * 1000 -
46- new Date ( ) . getTimezoneOffset ( ) * 60000 ,
47- )
48-
49- return [
50- token . name ,
51- new Date ( Date . parse ( token . createdOn + ' +0000' ) - new Date ( ) . getTimezoneOffset ( ) * 60000 )
52- . toISOString ( )
53- . slice ( 0 , 19 )
54- . replace ( 'T' , ' ' ) ,
55- expirationDate . toISOString ( ) . slice ( 0 , 19 ) . replace ( 'T' , ' ' ) ,
56- '[' + token . authorities . join ( ', ' ) + ']' ,
57- _ (
58- < Button
59- variant = 'danger'
60- onClick = { ( ) => {
61- revokeToken ( token . name ) . catch ( ( error ) => console . error ( error ) )
62- } }
63- >
64- { t ( 'Revoke Token' ) }
65- </ Button > ,
66- ) ,
67- ]
68- } )
69- setTableData ( tableData )
70- } else if ( response . status === HttpStatus . UNAUTHORIZED ) {
71- await signOut ( )
72- } else {
73- setTableData ( [ ] )
74- }
75- } catch ( error ) {
76- console . error ( 'Error fetching data:' , error )
77- setTableData ( [ ] )
34+ useEffect ( ( ) => {
35+ if ( session . status === 'unauthenticated' ) {
36+ void signOut ( )
7837 }
79- } , [ ] )
38+ } , [
39+ session ,
40+ ] )
8041
8142 const revokeToken = async ( tokenName : string ) => {
8243 try {
8344 const session = await getSession ( )
8445 if ( CommonUtils . isNullOrUndefined ( session ) ) return signOut ( )
8546 const response = await ApiUtils . DELETE (
86- CommonUtils . createUrlWithParams ( 'users/tokens' , { name : tokenName } ) ,
47+ CommonUtils . createUrlWithParams ( 'users/tokens' , {
48+ name : tokenName ,
49+ } ) ,
8750 session . user . access_token ,
8851 )
8952
9053 if ( response . status === HttpStatus . NO_CONTENT ) {
9154 MessageService . success ( t ( 'Revoke token sucessfully' ) )
92- await fetchData ( 'users/tokens' )
55+ setRevoked ( ! revoked )
9356 } else if ( response . status === HttpStatus . UNAUTHORIZED ) {
9457 await signOut ( )
9558 } else {
@@ -101,47 +64,139 @@ const TokensTable = ({ generatedToken }: Props): ReactNode => {
10164 }
10265 }
10366
67+ const columns = useMemo < ColumnDef < AccessToken > [ ] > (
68+ ( ) => [
69+ {
70+ id : 'name' ,
71+ header : t ( 'Token Name' ) ,
72+ cell : ( { row } ) => < > { row . original . name } </ > ,
73+ } ,
74+ {
75+ id : 'createdOn' ,
76+ name : t ( 'Created on' ) ,
77+ cell : ( { row } ) => (
78+ < >
79+ { new Date (
80+ Date . parse ( row . original . createdOn + ' +0000' ) - new Date ( ) . getTimezoneOffset ( ) * 60000 ,
81+ )
82+ . toISOString ( )
83+ . slice ( 0 , 19 )
84+ . replace ( 'T' , ' ' ) }
85+ </ >
86+ ) ,
87+ } ,
88+ {
89+ id : 'expiration-date' ,
90+ header : t ( 'Expiration Date' ) ,
91+ cell : ( { row } ) => {
92+ const expirationDate = new Date (
93+ Date . parse ( row . original . createdOn + ' +0000' ) +
94+ row . original . numberOfDaysValid * 24 * 60 * 60 * 1000 -
95+ new Date ( ) . getTimezoneOffset ( ) * 60000 ,
96+ )
97+ return < > { expirationDate . toISOString ( ) . slice ( 0 , 19 ) . replace ( 'T' , ' ' ) } </ >
98+ } ,
99+ } ,
100+ {
101+ id : 'authorities' ,
102+ header : t ( 'Authorities' ) ,
103+ cell : ( { row } ) => < > { '[' + row . original . authorities . join ( ', ' ) + ']' } </ > ,
104+ } ,
105+ {
106+ id : 'action' ,
107+ cell : ( { row } ) => (
108+ < Button
109+ variant = 'danger'
110+ onClick = { ( ) => {
111+ void revokeToken ( row . original . name )
112+ } }
113+ >
114+ { t ( 'Revoke Token' ) }
115+ </ Button >
116+ ) ,
117+ meta : {
118+ width : '10%' ,
119+ } ,
120+ } ,
121+ ] ,
122+ [
123+ t ,
124+ generatedToken ,
125+ revoked ,
126+ ] ,
127+ )
128+ const [ componentData , setComponentData ] = useState < AccessToken [ ] > ( ( ) => [ ] )
129+ const memoizedData = useMemo (
130+ ( ) => componentData ,
131+ [
132+ componentData ,
133+ ] ,
134+ )
135+ const [ showProcessing , setShowProcessing ] = useState ( false )
136+
104137 useEffect ( ( ) => {
105- fetchData ( 'users/tokens' ) . catch ( ( error ) => {
106- console . error ( error )
107- } )
108- } , [ fetchData , generatedToken ] )
109-
110- const columns = [
111- {
112- id : 'token-name' ,
113- name : t ( 'Token Name' ) ,
114- sort : false ,
115- } ,
116- {
117- id : 'created-on' ,
118- name : t ( 'Created on' ) ,
119- sort : false ,
120- } ,
121- {
122- id : 'expiration-date' ,
123- name : t ( 'Expiration Date' ) ,
124- sort : false ,
125- } ,
126- {
127- id : 'authorities' ,
128- name : t ( 'Authorities' ) ,
129- sort : false ,
130- } ,
131- {
132- id : 'action' ,
133- sort : false ,
134- width : '10%' ,
135- } ,
136- ]
138+ if ( session . status === 'loading' ) return
139+ const controller = new AbortController ( )
140+ const signal = controller . signal
141+
142+ const timeLimit = componentData . length !== 0 ? 700 : 0
143+ const timeout = setTimeout ( ( ) => {
144+ setShowProcessing ( true )
145+ } , timeLimit )
146+
147+ void ( async ( ) => {
148+ try {
149+ if ( CommonUtils . isNullOrUndefined ( session . data ) ) return signOut ( )
150+ const response = await ApiUtils . GET ( 'users/tokens' , session . data . user . access_token , signal )
151+ if ( response . status !== HttpStatus . OK ) {
152+ const err = ( await response . json ( ) ) as ErrorDetails
153+ throw new Error ( err . message )
154+ }
155+
156+ const data = ( await response . json ( ) ) as EmbeddedAccessTokens
157+ setComponentData (
158+ CommonUtils . isNullOrUndefined ( data [ '_embedded' ] [ 'sw360:restApiTokens' ] )
159+ ? [ ]
160+ : data [ '_embedded' ] [ 'sw360:restApiTokens' ] ,
161+ )
162+ } catch ( error ) {
163+ if ( error instanceof DOMException && error . name === 'AbortError' ) {
164+ return
165+ }
166+ const message = error instanceof Error ? error . message : String ( error )
167+ MessageService . error ( message )
168+ } finally {
169+ clearTimeout ( timeout )
170+ setShowProcessing ( false )
171+ }
172+ } ) ( )
173+
174+ return ( ) => controller . abort ( )
175+ } , [
176+ session ,
177+ revoked ,
178+ generatedToken ,
179+ ] )
180+
181+ const table = useReactTable ( {
182+ data : memoizedData ,
183+ columns,
184+ getCoreRowModel : getCoreRowModel ( ) ,
185+ } )
137186
138187 return (
139- < div className = 'row' >
140- { tableData . length > 0 && (
141- < Table
142- columns = { columns }
143- data = { tableData }
144- />
188+ < div className = 'mb-3' >
189+ { table ? (
190+ < >
191+ < SW360Table
192+ table = { table }
193+ showProcessing = { showProcessing }
194+ />
195+ </ >
196+ ) : (
197+ < div className = 'col-12 mt-1 text-center' >
198+ < Spinner className = 'spinner' />
199+ </ div >
145200 ) }
146201 </ div >
147202 )
0 commit comments