@@ -31,6 +31,7 @@ import { Button } from '@/components/common/Button'
3131import { toast } from 'react-toastify'
3232import CopyButton from '@/components/common/CopyButton'
3333import { SseLabel } from '@/components/apps/EncryptionModeIndicator'
34+ import clsx from 'clsx'
3435
3536export default function ServiceAccount ( { params } : { params : { team : string ; account : string } } ) {
3637 const { activeOrganisation : organisation } = useContext ( organisationContext )
@@ -319,74 +320,85 @@ export default function ServiceAccount({ params }: { params: { team: string; acc
319320 { userCanReadTokens ? (
320321 < div className = "space-y-2 divide-y divide-neutral-500/20 py-4" >
321322 { account . tokens && account . tokens . length > 0 ? (
322- account . tokens . map ( ( token ) => (
323- < div
324- key = { token ! . id }
325- className = "grid grid-cols-1 md:grid-cols-12 gap-4 items-center p-2 group"
326- >
327- { /* Token Name and ID*/ }
328- < div className = "md:col-span-4 space-y-1" >
329- < div className = "flex items-center gap-2" >
330- < FaKey className = "text-neutral-500 flex-shrink-0" />
331- < span className = "font-medium text-lg text-zinc-900 dark:text-zinc-100 truncate" >
332- { token ! . name }
333- </ span >
334- </ div >
335- < div className = "flex items-center gap-2 text-sm text-neutral-500" >
336- < span className = "text-neutral-500 text-xs flex items-center" >
337- Token ID:
338- </ span >
339- < CopyButton
340- value = { token ! . id }
341- buttonVariant = "ghost"
342- title = "Copy Token ID to clipboard"
343- >
344- < span className = "text-neutral-500 text-2xs font-mono" > { token ! . id } </ span >
345- </ CopyButton >
323+ account . tokens . map ( ( token ) => {
324+ const isExpired =
325+ token ! . expiresAt === null ? false : new Date ( token ! . expiresAt ) < new Date ( )
326+ return (
327+ < div
328+ key = { token ! . id }
329+ className = "grid grid-cols-1 md:grid-cols-12 gap-4 items-center p-2 group"
330+ >
331+ { /* Token Name and ID*/ }
332+ < div className = "md:col-span-4 space-y-1" >
333+ < div className = "flex items-center gap-2" >
334+ < FaKey className = "text-neutral-500 flex-shrink-0" />
335+ < span className = "font-medium text-lg text-zinc-900 dark:text-zinc-100 truncate" >
336+ { token ! . name }
337+ </ span >
338+ </ div >
339+ < div className = "flex items-center gap-2 text-sm text-neutral-500" >
340+ < span className = "text-neutral-500 text-xs flex items-center" >
341+ Token ID:
342+ </ span >
343+ < CopyButton
344+ value = { token ! . id }
345+ buttonVariant = "ghost"
346+ title = "Copy Token ID to clipboard"
347+ >
348+ < span className = "text-neutral-500 text-2xs font-mono" > { token ! . id } </ span >
349+ </ CopyButton >
350+ </ div >
346351 </ div >
347- </ div >
348352
349- { /* Created Info*/ }
350- < div className = "md:col-span-4 text-neutral-500 text-sm flex flex-col gap-1" >
351- < div className = "whitespace-nowrap" >
352- Created { relativeTimeFromDates ( new Date ( token ?. createdAt ) ) }
353- </ div >
354- < div className = "flex items-center gap-2" >
355- < span className = "text-neutral-500" > by:</ span >
356- < Avatar member = { token ! . createdBy ! } size = "sm" />
357- < span className = "font-medium text-zinc-900 dark:text-zinc-100" >
358- { token ?. createdBy ?. fullName }
359- </ span >
353+ { /* Created Info*/ }
354+ < div className = "md:col-span-4 text-neutral-500 text-sm flex flex-col gap-1" >
355+ < div className = "whitespace-nowrap" >
356+ Created { relativeTimeFromDates ( new Date ( token ?. createdAt ) ) }
357+ </ div >
358+ < div className = "flex items-center gap-2" >
359+ < span className = "text-neutral-500" > by</ span >
360+ < Avatar member = { token ! . createdBy ! } size = "sm" />
361+ < span className = "font-medium text-zinc-900 dark:text-zinc-100" >
362+ { token ?. createdBy ?. fullName }
363+ </ span >
364+ </ div >
360365 </ div >
361- </ div >
362366
363- { /* Token Status*/ }
364- < div className = "md:col-span-3 space-y-2" >
365- < div className = "flex items-center gap-1 text-sm text-neutral-500" >
366- < span className = "whitespace-nowrap" > Expires:</ span >
367- < span className = "whitespace-nowrap" >
368- { token ! . expiresAt
369- ? relativeTimeFromDates ( new Date ( token ?. expiresAt ) )
370- : 'never' }
371- </ span >
372- </ div >
367+ { /* Token Status*/ }
368+ < div className = "md:col-span-3 space-y-2" >
369+ < div
370+ className = { clsx (
371+ 'flex items-center gap-1 text-sm ' ,
372+ isExpired ? 'text-red-500' : 'text-neutral-500'
373+ ) }
374+ >
375+ < span className = "whitespace-nowrap" >
376+ { isExpired ? 'Expired' : 'Expires' }
377+ </ span >
378+ < span className = "whitespace-nowrap" >
379+ { token ! . expiresAt
380+ ? relativeTimeFromDates ( new Date ( token ?. expiresAt ) )
381+ : 'never' }
382+ </ span >
383+ </ div >
373384
374- < div className = "flex items-center gap-1 text-sm text-neutral-500" >
375- < span className = "whitespace-nowrap" > Last used:</ span >
376- < span className = "whitespace-nowrap" >
377- { token ! . lastUsed
378- ? relativeTimeFromDates ( new Date ( token ?. lastUsed ) )
379- : 'never' }
380- </ span >
385+ < div className = "flex items-center gap-1 text-sm text-neutral-500" >
386+ < span className = "whitespace-nowrap" > Last used</ span >
387+ < span className = "whitespace-nowrap" >
388+ { token ! . lastUsed
389+ ? relativeTimeFromDates ( new Date ( token ?. lastUsed ) )
390+ : 'never' }
391+ </ span >
392+ </ div >
381393 </ div >
382- </ div >
383394
384- { /* Delete Button*/ }
385- < div className = "md:col-span-1 flex justify-end opacity-0 group-hover:opacity-100 transition ease" >
386- < DeleteServiceAccountTokenDialog token = { token ! } />
395+ { /* Delete Button*/ }
396+ < div className = "md:col-span-1 flex justify-end opacity-0 group-hover:opacity-100 transition ease" >
397+ < DeleteServiceAccountTokenDialog token = { token ! } />
398+ </ div >
387399 </ div >
388- </ div >
389- ) )
400+ )
401+ } )
390402 ) : (
391403 < div className = "py-8" >
392404 < EmptyState
0 commit comments