Remix 3
- Remix 3 components running on the client, backed by Remix 3
- routing in the worker.
+ Remix 3 components server-rendered by the worker, then hydrated
+ for client navigation.
diff --git a/client/routes/index.tsx b/client/routes/index.tsx
index f0c7aa8..7b6b327 100644
--- a/client/routes/index.tsx
+++ b/client/routes/index.tsx
@@ -1,3 +1,4 @@
+import { routes } from '#server/routes.ts'
import { AccountRoute } from './account.tsx'
import { ChatRoute } from './chat.tsx'
import { HomeRoute } from './home.tsx'
@@ -7,13 +8,13 @@ import { OAuthCallbackRoute } from './oauth-callback.tsx'
import { ResetPasswordRoute } from './reset-password.tsx'
export const clientRoutes = {
- '/': ,
- '/chat': ,
+ [routes.home.href()]: ,
+ [routes.chat.href()]: ,
'/chat/:threadId': ,
- '/account': ,
- '/login': ,
- '/signup': ,
- '/reset-password': ,
- '/oauth/authorize': ,
- '/oauth/callback': ,
+ [routes.account.href()]: ,
+ [routes.login.href()]: ,
+ [routes.signup.href()]: ,
+ [routes.resetPassword.href()]: ,
+ [routes.oauthAuthorize.href()]: ,
+ [routes.oauthCallback.href()]: ,
}
diff --git a/client/routes/login.tsx b/client/routes/login.tsx
index ed312b9..24b2b16 100644
--- a/client/routes/login.tsx
+++ b/client/routes/login.tsx
@@ -4,6 +4,8 @@ import {
getPathname,
listenToRouterNavigation,
} from '#client/client-router.tsx'
+import { readRouterSearch } from '#client/router-location.tsx'
+import { routes } from '#server/routes.ts'
import { fetchSessionInfo, type SessionStatus } from '#client/session.ts'
import {
colors,
@@ -15,10 +17,16 @@ import {
} from '#client/styles/tokens.ts'
type AuthMode = 'login' | 'signup'
type AuthStatus = 'idle' | 'submitting' | 'success' | 'error'
-function getSearchParams() {
- return typeof window === 'undefined'
- ? new URLSearchParams()
- : new URLSearchParams(window.location.search)
+function getSearchParams(handle: Handle) {
+ if (typeof window !== 'undefined') {
+ return new URLSearchParams(window.location.search)
+ }
+
+ try {
+ return new URLSearchParams(readRouterSearch(handle))
+ } catch {
+ return new URLSearchParams()
+ }
}
function normalizeRedirectTo(value: string | null) {
if (!value) return null
@@ -27,24 +35,24 @@ function normalizeRedirectTo(value: string | null) {
return value
}
function buildAuthPath(mode: AuthMode, redirectTo: string | null) {
- const path = mode === 'signup' ? '/signup' : '/login'
+ const path = mode === 'signup' ? routes.signup.href() : routes.login.href()
return buildAuthLink(path, redirectTo)
}
function getAuthModeFromPathname(pathname: string): AuthMode {
- return pathname === '/signup' ? 'signup' : 'login'
+ return pathname === routes.signup.href() ? 'signup' : 'login'
}
-function getCurrentAuthMode() {
- return getAuthModeFromPathname(getPathname())
+function getCurrentAuthMode(handle: Handle) {
+ return getAuthModeFromPathname(getPathname(handle))
}
-function getCurrentRedirectTo() {
- return normalizeRedirectTo(getSearchParams().get('redirectTo'))
+function getCurrentRedirectTo(handle: Handle) {
+ return normalizeRedirectTo(getSearchParams(handle).get('redirectTo'))
}
export function LoginRoute(handle: Handle) {
let status: AuthStatus = 'idle'
let message: string | null = null
let sessionStatus: SessionStatus = 'idle'
let sessionEmail = ''
- let activeMode = getCurrentAuthMode()
+ let activeMode = getCurrentAuthMode(handle)
let routePath: string | null = null
function setState(nextStatus: AuthStatus, nextMessage: string | null = null) {
status = nextStatus
@@ -57,30 +65,34 @@ export function LoginRoute(handle: Handle) {
}
listenToRouterNavigation(handle, () => {
if (!routePath) return
- if (getPathname() !== routePath) {
+ if (getPathname(handle) !== routePath) {
resetAuthState()
}
})
- handle.queueTask(async (signal) => {
- if (sessionStatus !== 'idle') return
- sessionStatus = 'loading'
- const session = await fetchSessionInfo(signal)
- if (signal.aborted) return
- sessionEmail = session?.email ?? ''
- sessionStatus = 'ready'
- if (sessionEmail && typeof window !== 'undefined') {
- window.location.assign(getCurrentRedirectTo() ?? '/account')
- return
- }
- handle.update()
- })
+ if (typeof window !== 'undefined') {
+ handle.queueTask(async (signal) => {
+ if (sessionStatus !== 'idle') return
+ sessionStatus = 'loading'
+ const session = await fetchSessionInfo(signal)
+ if (signal.aborted) return
+ sessionEmail = session?.email ?? ''
+ sessionStatus = 'ready'
+ if (sessionEmail) {
+ window.location.assign(
+ getCurrentRedirectTo(handle) ?? routes.account.href(),
+ )
+ return
+ }
+ handle.update()
+ })
+ }
async function handleSubmit(event: SubmitEvent) {
event.preventDefault()
if (!(event.currentTarget instanceof HTMLFormElement)) return
const formData = new FormData(event.currentTarget)
const email = String(formData.get('email') ?? '').trim()
const password = String(formData.get('password') ?? '')
- const mode = getCurrentAuthMode()
+ const mode = getCurrentAuthMode(handle)
const rememberMe = mode === 'login' && formData.get('rememberMe') === 'on'
if (!email || !password) {
setState('error', 'Email and password are required.')
@@ -104,22 +116,24 @@ export function LoginRoute(handle: Handle) {
return
}
if (typeof window !== 'undefined') {
- window.location.assign(getCurrentRedirectTo() ?? '/account')
+ window.location.assign(
+ getCurrentRedirectTo(handle) ?? routes.account.href(),
+ )
}
} catch {
setState('error', 'Network error. Please try again.')
}
}
return () => {
- const mode = getCurrentAuthMode()
+ const mode = getCurrentAuthMode(handle)
if (!routePath) {
- routePath = getPathname()
+ routePath = getPathname(handle)
}
if (mode !== activeMode) {
activeMode = mode
resetAuthState()
}
- const redirectTo = getCurrentRedirectTo()
+ const redirectTo = getCurrentRedirectTo(handle)
const isSignup = mode === 'signup'
const isSubmitting = status === 'submitting'
const title = isSignup ? 'Create your account' : 'Welcome back'
@@ -344,7 +358,7 @@ export function LoginRoute(handle: Handle) {
{!isSignup ? (