Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ jobs:
name: 🚀 Deploy to production
needs: sha-guard
if: needs.sha-guard.outputs.should_deploy == 'true'
env:
CLOUDFLARE_ACCOUNT_ID:
${{ vars.CLOUDFLARE_ACCOUNT_ID || secrets.CLOUDFLARE_ACCOUNT_ID }}
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ jobs:
deploy:
runs-on: ubuntu-latest
name: 🔎 Deploy Preview Resources
env:
CLOUDFLARE_ACCOUNT_ID:
${{ vars.CLOUDFLARE_ACCOUNT_ID || secrets.CLOUDFLARE_ACCOUNT_ID }}
environment:
name:
preview-${{ github.event.pull_request.number || inputs.pr_number ||
Expand Down Expand Up @@ -433,6 +436,9 @@ jobs:
cleanup:
runs-on: ubuntu-latest
name: 🧹 Cleanup Preview Resources
env:
CLOUDFLARE_ACCOUNT_ID:
${{ vars.CLOUDFLARE_ACCOUNT_ID || secrets.CLOUDFLARE_ACCOUNT_ID }}
if: >-
(github.event_name == 'pull_request' &&
github.event.action == 'closed' &&
Expand Down
31 changes: 31 additions & 0 deletions client/app-root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { clientEntry, type EntryComponent, type Handle } from 'remix/ui'
import { type AppLoaderData } from '#shared/loader-data.ts'
import { App } from './app.tsx'
import { AppLoaderDataProvider } from './loader-data-context.tsx'
import { RouterLocationProvider } from './router-location.tsx'
import { type SessionInfo } from './session.ts'

export const APP_ROOT_ENTRY_ID = '/client-entry.js#AppRoot'

export type AppRootProps = {
url: string
session: SessionInfo | null
loaderData?: AppLoaderData
notFound?: boolean
}

export const AppRoot: EntryComponent<AppRootProps> = clientEntry(
APP_ROOT_ENTRY_ID,
function AppRoot(handle: Handle<AppRootProps>) {
return () => (
<RouterLocationProvider url={handle.props.url}>
<AppLoaderDataProvider loaderData={handle.props.loaderData}>
<App
embeddedSession={handle.props.session}
notFound={handle.props.notFound === true}
/>
</AppLoaderDataProvider>
</RouterLocationProvider>
)
},
)
116 changes: 76 additions & 40 deletions client/app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { css, type Handle } from 'remix/ui'
import { clientRoutes } from './routes/index.tsx'
import { routes } from '#server/routes.ts'
import { clientRouteLoaders, clientRoutes } from './routes/index.tsx'
import {
getPathname,
listenToRouterNavigation,
Expand All @@ -10,14 +11,21 @@ import {
type SessionInfo,
type SessionStatus,
} from './session.ts'
import { SessionProvider } from './session-context.tsx'
import { buildAuthLink } from './auth-links.ts'
import { colors, mq, spacing, typography } from './styles/tokens.ts'
export function App(handle: Handle) {
let session: SessionInfo | null = null
let sessionStatus: SessionStatus = 'idle'

type AppProps = {
embeddedSession?: SessionInfo | null
notFound?: boolean
}

export function App(handle: Handle<AppProps>) {
let session: SessionInfo | null = handle.props?.embeddedSession ?? null
let sessionStatus: SessionStatus = 'ready'
let sessionRefreshInFlight = false
let sessionRefreshQueued = false
let currentPathname = getPathname()
let currentPathname = getPathname(handle)
function queueSessionRefresh() {
sessionRefreshQueued = true
if (sessionRefreshInFlight) return
Expand All @@ -29,9 +37,22 @@ export function App(handle: Handle) {
sessionRefreshQueued = false
sessionRefreshInFlight = true
handle.queueTask(async (signal) => {
const nextSession = await fetchSessionInfo(signal)
let nextSession: SessionInfo | null = null
try {
nextSession = await fetchSessionInfo(signal)
} catch (error) {
sessionRefreshInFlight = false
if (signal.aborted) return
console.error('Session refresh failed', error)
sessionStatus = 'ready'
handle.update()
return
}
sessionRefreshInFlight = false
if (signal.aborted) return
if (signal.aborted) {
if (sessionRefreshQueued) queueSessionRefresh()
return
}
session = nextSession
sessionStatus = 'ready'
handle.update()
Expand All @@ -43,11 +64,13 @@ export function App(handle: Handle) {
handle.update()
}
}
handle.queueTask(() => {
Comment thread
cursor[bot] marked this conversation as resolved.
queueSessionRefresh()
})
if (typeof window !== 'undefined') {
handle.queueTask(() => {
queueSessionRefresh()
})
}
listenToRouterNavigation(handle, () => {
currentPathname = getPathname()
currentPathname = getPathname(handle)
queueSessionRefresh()
handle.update()
})
Expand Down Expand Up @@ -85,11 +108,12 @@ export function App(handle: Handle) {
const isLoggedIn = isSessionReady && Boolean(sessionEmail)
const showAuthLinks = isSessionReady && !isLoggedIn
const oauthRedirectTo =
typeof window !== 'undefined' && currentPathname === '/oauth/authorize'
typeof window !== 'undefined' &&
currentPathname === routes.oauthAuthorize.href()
? `${currentPathname}${window.location.search}`
: null
const loginHref = buildAuthLink('/login', oauthRedirectTo)
const signupHref = buildAuthLink('/signup', oauthRedirectTo)
const loginHref = buildAuthLink(routes.login.href(), oauthRedirectTo)
const signupHref = buildAuthLink(routes.signup.href(), oauthRedirectTo)
return (
<main
mix={[
Expand Down Expand Up @@ -126,7 +150,11 @@ export function App(handle: Handle) {
}),
]}
>
<a href="/" aria-label="Home" mix={[css(navHomeLinkCss)]}>
<a
href={routes.home.href()}
aria-label="Home"
mix={[css(navHomeLinkCss)]}
>
<img
src="/logo.png"
alt=""
Expand All @@ -153,42 +181,50 @@ export function App(handle: Handle) {
) : null}
{isLoggedIn ? (
<>
<a href="/chat" mix={[css(navLinkCss)]}>
<a href={routes.chat.href()} mix={[css(navLinkCss)]}>
Chat
</a>
<a href="/account" mix={[css(navLinkCss)]}>
<a href={routes.account.href()} mix={[css(navLinkCss)]}>
{sessionEmail}
</a>
<form method="post" action="/logout" mix={[css({ margin: 0 })]}>
<form
method="post"
action={routes.logout.href()}
mix={[css({ margin: 0 })]}
>
<button type="submit" mix={[css(logOutButtonCss)]}>
Log out
</button>
</form>
</>
) : null}
</nav>
<Router
routes={clientRoutes}
fallback={
<section>
<h2
mix={[
css({
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.semibold,
marginBottom: spacing.sm,
color: colors.text,
}),
]}
>
Not Found
</h2>
<p mix={[css({ color: colors.textMuted })]}>
That route does not exist.
</p>
</section>
}
/>
<SessionProvider session={session}>
<Router
loaders={clientRouteLoaders}
routes={clientRoutes}
notFound={handle.props?.notFound === true}
fallback={
<section>
<h2
mix={[
css({
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.semibold,
marginBottom: spacing.sm,
color: colors.text,
}),
]}
>
Not Found
</h2>
<p mix={[css({ color: colors.textMuted })]}>
That route does not exist.
</p>
</section>
}
/>
</SessionProvider>
</main>
)
}
Expand Down
Loading
Loading