diff --git a/client/app.tsx b/client/app.tsx
index 94cc85b..054c637 100644
--- a/client/app.tsx
+++ b/client/app.tsx
@@ -6,14 +6,13 @@ import {
OAuthCallbackRoute,
} from './client-routes.tsx'
import { Router } from './client-router.tsx'
+import {
+ fetchSessionInfo,
+ type SessionInfo,
+ type SessionStatus,
+} from './session.ts'
import { colors, spacing, typography } from './styles/tokens.ts'
-type SessionInfo = {
- email: string
-}
-
-type SessionStatus = 'idle' | 'loading' | 'ready'
-
type NavLink = {
href: string
label: string
@@ -27,22 +26,7 @@ export function App(handle: Handle) {
if (sessionStatus !== 'idle') return
sessionStatus = 'loading'
- try {
- const response = await fetch('/session', {
- headers: { Accept: 'application/json' },
- credentials: 'include',
- })
- const payload = await response.json().catch(() => null)
- const email =
- response.ok &&
- payload?.ok &&
- typeof payload?.session?.email === 'string'
- ? payload.session.email.trim()
- : ''
- session = email ? { email } : null
- } catch {
- session = null
- }
+ session = await fetchSessionInfo()
sessionStatus = 'ready'
handle.update()
diff --git a/client/client-routes.tsx b/client/client-routes.tsx
index a8bf8c6..31b25a8 100644
--- a/client/client-routes.tsx
+++ b/client/client-routes.tsx
@@ -1,6 +1,11 @@
import { type Handle } from 'remix/component'
import { navigate } from './client-router.tsx'
import { Counter } from './counter.tsx'
+import {
+ fetchSessionInfo,
+ type SessionInfo,
+ type SessionStatus,
+} from './session.ts'
import {
colors,
radius,
@@ -354,6 +359,8 @@ function OAuthAuthorizeForm(handle: Handle) {
let message: OAuthAuthorizeMessage | null = null
let submitting = false
let lastSearch = ''
+ let session: SessionInfo | null = null
+ let sessionStatus: SessionStatus = 'idle'
function setMessage(next: OAuthAuthorizeMessage | null) {
message = next
@@ -420,6 +427,16 @@ function OAuthAuthorizeForm(handle: Handle) {
}
}
+ async function loadSession() {
+ if (sessionStatus !== 'idle') return
+ sessionStatus = 'loading'
+
+ session = await fetchSessionInfo()
+
+ sessionStatus = 'ready'
+ handle.update()
+ }
+
async function submitDecision(
decision: 'approve' | 'deny',
form?: HTMLFormElement,
@@ -486,7 +503,11 @@ function OAuthAuthorizeForm(handle: Handle) {
async function handleSubmit(event: SubmitEvent) {
event.preventDefault()
if (!(event.currentTarget instanceof HTMLFormElement)) return
- await submitDecision('approve', event.currentTarget)
+ const hasSession = Boolean(session?.email)
+ await submitDecision(
+ 'approve',
+ hasSession ? undefined : event.currentTarget,
+ )
}
return () => {
@@ -496,12 +517,26 @@ function OAuthAuthorizeForm(handle: Handle) {
lastSearch = currentSearch
void loadInfo()
}
+ if (sessionStatus === 'idle') {
+ void loadSession()
+ }
const clientLabel = info?.client?.name ?? 'Unknown client'
const scopes = info?.scopes ?? []
const scopeLabel =
scopes.length > 0 ? scopes.join(', ') : 'No scopes requested.'
- const actionsDisabled = status !== 'ready' || submitting
+ const sessionEmail = session?.email ?? ''
+ const isSessionReady = sessionStatus === 'ready'
+ const isSessionLoading =
+ sessionStatus === 'loading' || sessionStatus === 'idle'
+ const isLoggedIn = isSessionReady && Boolean(sessionEmail)
+ const actionsDisabled = status !== 'ready' || submitting || isSessionLoading
+ const formReady = status === 'ready' && !isSessionLoading
+ const authorizeLabel = submitting
+ ? 'Submitting...'
+ : isLoggedIn
+ ? 'Approve connection'
+ : 'Authorize'
return (
+ {isSessionLoading ? (
+
Checking your session…
+ ) : null}
+ {isLoggedIn ? (
+
+
+ Signed in as {sessionEmail}
+
+
+ Approve to continue with this account.
+
+
+ ) : null}
{status === 'loading' ? (
Loading authorization details…
@@ -573,62 +636,66 @@ function OAuthAuthorizeForm(handle: Handle) {
border: `1px solid ${colors.border}`,
backgroundColor: colors.surface,
boxShadow: shadows.sm,
- opacity: status === 'ready' ? 1 : 0.7,
+ opacity: formReady ? 1 : 0.7,
}}
on={{ submit: handleSubmit }}
>
-
-
+ {!isLoggedIn && isSessionReady ? (
+ <>
+
+
+ >
+ ) : null}