|
14 | 14 | console.debug("USER DATA:", JSON.stringify(userData)); |
15 | 15 |
|
16 | 16 | const opeyConsentInfo = data.opeyConsentInfo || null; |
| 17 | + const sessionInfo = data.sessionInfo || null; |
| 18 | +
|
| 19 | + function formatTimeRemaining(seconds: number | null): string { |
| 20 | + if (seconds === null) return "Unknown"; |
| 21 | + if (seconds <= 0) return "Expired"; |
| 22 | + const hours = Math.floor(seconds / 3600); |
| 23 | + const minutes = Math.floor((seconds % 3600) / 60); |
| 24 | + if (hours > 0) return `${hours}h ${minutes}m`; |
| 25 | + if (minutes > 0) return `${minutes}m`; |
| 26 | + return `${seconds}s`; |
| 27 | + } |
| 28 | +
|
| 29 | + function tokenStatusColor(seconds: number | null): string { |
| 30 | + if (seconds === null) return "bg-surface-300 dark:bg-surface-600"; |
| 31 | + if (seconds <= 0) return "bg-error-500"; |
| 32 | + if (seconds < 300) return "bg-warning-500"; |
| 33 | + return "bg-success-500"; |
| 34 | + } |
17 | 35 |
|
18 | 36 | function formatDateFromUnix(epochDateMilliseconds: number | string): string { |
19 | 37 | console.log("Formatting date:", epochDateMilliseconds); |
|
361 | 379 | <div class="flex flex-col space-y-6"> |
362 | 380 | {@render userInfo(userData)} |
363 | 381 |
|
| 382 | + <!-- Session Info Section --> |
| 383 | + <div class="mx-10 pr-5"> |
| 384 | + <header class="py-4"> |
| 385 | + <h1 class="h4 text-center align-middle">Session Info</h1> |
| 386 | + </header> |
| 387 | + {#if sessionInfo && sessionInfo.hasAccessToken} |
| 388 | + <article class="border-primary-500 space-y-1 border-b-[1px] p-4"> |
| 389 | + {#each [ |
| 390 | + { label: "Session ID", value: sessionInfo.sessionId }, |
| 391 | + { label: "OAuth Provider", value: sessionInfo.oauthProvider }, |
| 392 | + ] as item} |
| 393 | + <div class="hover:bg-primary-500/5 flex items-center justify-between rounded-md p-2" data-testid={`session-${item.label.toLowerCase().replace(/\s+/g, "-")}`}> |
| 394 | + <strong>{item.label}</strong> |
| 395 | + <span class="rounded-sm bg-gray-800/20 p-2 font-mono text-sm backdrop-blur-2xl"> |
| 396 | + {item.value || "N/A"} |
| 397 | + </span> |
| 398 | + </div> |
| 399 | + <hr class="hr !my-0 opacity-20" /> |
| 400 | + {/each} |
| 401 | + |
| 402 | + <!-- Access Token --> |
| 403 | + <div class="hover:bg-primary-500/5 flex items-center justify-between rounded-md p-2" data-testid="session-access-token"> |
| 404 | + <div class="flex items-center gap-2"> |
| 405 | + <strong>Access Token</strong> |
| 406 | + <span class="h-2.5 w-2.5 rounded-full {tokenStatusColor(sessionInfo.accessTokenExpiresInSeconds)}" title={sessionInfo.accessTokenExpiresInSeconds !== null && sessionInfo.accessTokenExpiresInSeconds <= 0 ? "Expired" : "Active"}></span> |
| 407 | + </div> |
| 408 | + <div class="flex items-center gap-3"> |
| 409 | + <span class="rounded-sm bg-gray-800/20 p-2 font-mono text-sm backdrop-blur-2xl"> |
| 410 | + {sessionInfo.accessTokenPreview || "N/A"} |
| 411 | + </span> |
| 412 | + <span class="text-xs text-surface-600 dark:text-surface-400"> |
| 413 | + {#if sessionInfo.accessTokenExpiresInSeconds !== null && sessionInfo.accessTokenExpiresInSeconds <= 0} |
| 414 | + Expired |
| 415 | + {:else} |
| 416 | + {formatTimeRemaining(sessionInfo.accessTokenExpiresInSeconds)} remaining |
| 417 | + {/if} |
| 418 | + </span> |
| 419 | + </div> |
| 420 | + </div> |
| 421 | + <hr class="hr !my-0 opacity-20" /> |
| 422 | + |
| 423 | + <!-- Refresh Token --> |
| 424 | + <div class="hover:bg-primary-500/5 flex items-center justify-between rounded-md p-2" data-testid="session-refresh-token"> |
| 425 | + <div class="flex items-center gap-2"> |
| 426 | + <strong>Refresh Token</strong> |
| 427 | + {#if sessionInfo.hasRefreshToken} |
| 428 | + <span class="h-2.5 w-2.5 rounded-full {tokenStatusColor(sessionInfo.refreshTokenExpiresInSeconds)}" title={sessionInfo.refreshTokenExpiresInSeconds !== null && sessionInfo.refreshTokenExpiresInSeconds <= 0 ? "Expired" : "Active"}></span> |
| 429 | + {:else} |
| 430 | + <span class="h-2.5 w-2.5 rounded-full bg-error-500" title="Missing"></span> |
| 431 | + {/if} |
| 432 | + </div> |
| 433 | + <div class="flex items-center gap-3"> |
| 434 | + {#if sessionInfo.hasRefreshToken} |
| 435 | + <span class="rounded-sm bg-gray-800/20 p-2 font-mono text-sm backdrop-blur-2xl"> |
| 436 | + {sessionInfo.refreshTokenPreview} |
| 437 | + </span> |
| 438 | + <span class="text-xs text-surface-600 dark:text-surface-400"> |
| 439 | + {#if sessionInfo.refreshTokenExpiresAt === null} |
| 440 | + Expiry unknown (not a JWT) |
| 441 | + {:else if sessionInfo.refreshTokenExpiresInSeconds !== null && sessionInfo.refreshTokenExpiresInSeconds <= 0} |
| 442 | + Expired |
| 443 | + {:else} |
| 444 | + {formatTimeRemaining(sessionInfo.refreshTokenExpiresInSeconds)} remaining |
| 445 | + {/if} |
| 446 | + </span> |
| 447 | + {:else} |
| 448 | + <span class="text-xs text-error-600 dark:text-error-400"> |
| 449 | + None — session cannot auto-renew |
| 450 | + </span> |
| 451 | + {/if} |
| 452 | + </div> |
| 453 | + </div> |
| 454 | + <hr class="hr !my-0 opacity-20" /> |
| 455 | + </article> |
| 456 | + {:else} |
| 457 | + <article class="border-primary-500 border-b-[1px] p-4"> |
| 458 | + <div class="rounded-lg bg-warning-50 p-3 dark:bg-warning-900/20" data-testid="session-warning"> |
| 459 | + <p class="text-sm text-warning-700 dark:text-warning-300"> |
| 460 | + No active session found. Your session may have expired. |
| 461 | + <a href="/login" class="font-medium underline hover:text-warning-900 dark:hover:text-warning-100">Log in again</a> to restore full functionality. |
| 462 | + </p> |
| 463 | + </div> |
| 464 | + </article> |
| 465 | + {/if} |
| 466 | + </div> |
| 467 | + |
364 | 468 | <!-- Preferences Section --> |
365 | 469 | <div class="mx-10 pr-5"> |
366 | 470 | <header class="flex items-center justify-between py-4"> |
|
0 commit comments