Skip to content

Commit e1c13d7

Browse files
committed
feat: Add SWR middleware for clerk auth + universal login example
1 parent 0c2917d commit e1c13d7

File tree

39 files changed

+563
-139
lines changed

39 files changed

+563
-139
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// -i- Automatically generated by 'yarn link-routes', do not modify manually
2+
export { default } from 'user-management/routes/me/index'

apps/expo/app/_layout.tsx

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,58 @@
11
import { useEffect } from 'react'
22
import { Stack, SplashScreen } from 'expo-router'
3-
// Layouts
43
import RootLayout from 'app/routes/layout'
5-
// Config
64
import tailwindConfig from 'app/tailwind.config'
7-
// Context
85
import { AetherContextManager } from 'aetherspace/context'
9-
// Assets
6+
import { useAuth } from '@aetherspace/clerk-auth/hooks'
107
import * as assets from 'registries/assets.generated'
11-
// Hooks
128
import useLoadFonts from 'app/hooks/useLoadFonts'
9+
import { setGlobal } from 'aetherspace/utils'
1310

1411
/* --- Config ---------------------------------------------------------------------------------- */
1512

1613
SplashScreen.preventAutoHideAsync()
1714

15+
/* --- <AetherContextWrapper/> ----------------------------------------------------------------- */
16+
17+
const AetherContextWrapper = () => {
18+
// Auth
19+
const { getToken } = useAuth()
20+
21+
// -- Effects --
22+
23+
useEffect(() => {
24+
setGlobal('getAuthToken', getToken)
25+
}, [getToken])
26+
27+
// -- Render --
28+
29+
return (
30+
<AetherContextManager
31+
assets={assets}
32+
icons={{}}
33+
twConfig={tailwindConfig}
34+
getAuthToken={getToken}
35+
isAppDir
36+
isExpo
37+
>
38+
<Stack
39+
screenOptions={{
40+
headerShown: false,
41+
contentStyle: { backgroundColor: '#FFFFFF' },
42+
animation: 'slide_from_right',
43+
}}
44+
/>
45+
</AetherContextManager>
46+
)
47+
}
48+
1849
/* --- <ExpoRootLayout/> ----------------------------------------------------------------------- */
1950

2051
const ExpoRootLayout = () => {
2152
// Hide app when fonts not yet loaded
2253
const fontsLoaded = useLoadFonts()
2354

24-
// -- Splash --
55+
// -- Effects --
2556

2657
useEffect(() => {
2758
// Hide the splash screen after the fonts have loaded and the UI is ready
@@ -34,15 +65,7 @@ const ExpoRootLayout = () => {
3465

3566
return (
3667
<RootLayout>
37-
<AetherContextManager assets={assets} icons={{}} twConfig={tailwindConfig} isExpo isAppDir>
38-
<Stack
39-
screenOptions={{
40-
headerShown: false,
41-
contentStyle: { backgroundColor: '#FFFFFF' },
42-
animation: 'slide_from_right',
43-
}}
44-
/>
45-
</AetherContextManager>
68+
<AetherContextWrapper />
4669
</RootLayout>
4770
)
4871
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
'use client'
2+
export { default, dynamic } from 'user-management/routes/me/index'

apps/next/app/ClientRootLayout.tsx

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
'use client'
2-
// Analytics
2+
import { useEffect } from 'react'
33
import { Analytics } from '@vercel/analytics/react'
4-
// Config
54
import tailwindConfig from 'app/tailwind.config'
6-
// Context
75
import { AetherContextManager } from 'aetherspace/context'
8-
// Hooks
6+
import { useAuth } from '@aetherspace/clerk-auth/hooks'
97
import useLoadFonts from 'app/hooks/useLoadFonts'
10-
// Utils
11-
import { setPublicEnvVars } from 'aetherspace/utils'
8+
import { setPublicEnvVars, setGlobal } from 'aetherspace/utils'
129

1310
/* --- Public Env Vars ------------------------------------------------------------------------- */
1411

@@ -29,16 +26,32 @@ const NextClientRootLayout = (props: { children: React.ReactNode }) => {
2926
// Props
3027
const { children } = props
3128

29+
// Auth
30+
const { getToken } = useAuth()
31+
3232
// -- Fonts --
3333

3434
useLoadFonts()
3535

36+
// -- Effects --
37+
38+
useEffect(() => {
39+
setGlobal('getAuthToken', getToken)
40+
}, [getToken])
41+
3642
// -- Render --
3743

3844
return (
3945
<>
4046
<Analytics />
41-
<AetherContextManager assets={{}} icons={{}} twConfig={tailwindConfig} isNextJS isAppDir>
47+
<AetherContextManager
48+
assets={{}}
49+
icons={{}}
50+
twConfig={tailwindConfig}
51+
getAuthToken={getToken}
52+
isAppDir
53+
isNextJS
54+
>
4255
{children}
4356
</AetherContextManager>
4457
</>

apps/next/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default authMiddleware({
1313
// Create the request context header (to pass things like auth, user, etc. to the API)
1414
const headerContext = await createRequestContext(req, { auth, evt })
1515
const extraHeaders = {
16-
'x-custom-header': 'custom-value',
16+
// 'x-custom-header': 'custom-value',
1717
}
1818

1919
// Execute the request handler (and pass the request context header)

apps/next/schema.graphql

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](
1111
scalar JSONObject
1212

1313
input GetRequestContextArgs {
14-
"""Echoes back the input"""
15-
echo: String
14+
"""Returns extra debug info"""
15+
debug: String
1616
}
1717

1818
type ClerkActor {
@@ -329,7 +329,7 @@ type ClerkUserProperties {
329329
"""
330330
A getter boolean to check if the user has uploaded an image or one was copied from OAuth. Returns false if Clerk is displaying an avatar for the user.
331331
"""
332-
hasImage: Boolean!
332+
hasImage: Boolean
333333

334334
"""Information about the user's primary email address."""
335335
primaryEmailAddress: ClerkUserEmailAdress
@@ -342,10 +342,10 @@ type ClerkUserProperties {
342342
"""
343343
An array of all the EmailAddress objects associated with the user. Includes the primary.
344344
"""
345-
emailAdresses: [ClerkUserEmailAdress]!
345+
emailAddresses: [ClerkUserEmailAdress]!
346346

347347
"""A getter boolean to check if the user has verified an email address."""
348-
hasVerifiedEmailAddress: Boolean!
348+
hasVerifiedEmailAddress: Boolean
349349

350350
"""Information about the user's primary phone number."""
351351
primaryPhoneNumber: ClerkUserPhoneNumber
@@ -361,49 +361,49 @@ type ClerkUserProperties {
361361
phoneNumbers: [ClerkUserPhoneNumber]!
362362

363363
"""A getter boolean to check if the user has verified a phone number."""
364-
hasVerifiedPhoneNumber: Boolean!
364+
hasVerifiedPhoneNumber: Boolean
365365

366366
"""
367367
An array of all the ExternalAccount objects associated with the user via OAuth. Note: This includes both verified & unverified external accounts.
368368
"""
369369
externalAccounts: [ClerkExternalAccount]!
370370

371371
"""A getter for the user's list of verified external accounts."""
372-
verifiedExternalAccounts: [ClerkExternalAccount]!
372+
verifiedExternalAccounts: [ClerkExternalAccount]
373373

374374
"""A getter for the user's list of unverified external accounts."""
375-
unverifiedExternalAccounts: [ClerkExternalAccount]!
375+
unverifiedExternalAccounts: [ClerkExternalAccount]
376376

377377
"""
378378
A list of OrganizationMemberships representing the list of organizations the user is member with.
379379
"""
380-
organizationMemberships: [ClerkOrganizationMembership]!
380+
organizationMemberships: [ClerkOrganizationMembership]
381381

382382
"""A boolean indicating whether the user has a password on their account."""
383-
passwordEnabled: Boolean!
383+
passwordEnabled: Boolean
384384

385385
"""
386386
A boolean indicating whether the user has enabled TOTP by generating a TOTP secret and verifying it via an authenticator app.
387387
"""
388-
totpEnabled: Boolean!
388+
totpEnabled: Boolean
389389

390390
"""
391391
A boolean indicating whether the user has enabled two-factor authentication.
392392
"""
393-
twoFactorEnabled: Boolean!
393+
twoFactorEnabled: Boolean
394394

395395
"""A boolean indicating whether the user has enabled Backup codes."""
396-
backupCodeEnabled: Boolean!
396+
backupCodeEnabled: Boolean
397397

398398
"""
399399
A boolean indicating whether the organization creation is enabled for the user or not.
400400
"""
401-
createOrganizationEnabled: Boolean!
401+
createOrganizationEnabled: Boolean
402402

403403
"""
404404
A boolean indicating whether the user is able to delete their own account or not.
405405
"""
406-
deleteSelfEnabled: Boolean!
406+
deleteSelfEnabled: Boolean
407407

408408
"""
409409
Date when the user last signed in. May be empty if the user has never signed in.

features/user-management/resolvers/getRequestContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { GetRequestContextResponse } from '../schemas/GetRequestContextResponse'
77
export const getRequestContext = aetherResolver(
88
async ({ args, parseArgs, withDefaults, handleError, context, user }) => {
99
try {
10-
const clerkUser = context?.user as GetRequestContextResponse['user']
10+
const clerkUser = (user || context?.user) as GetRequestContextResponse['user']
1111

1212
// -- Get user from DB --
1313

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react'
2+
import { AetherPage } from 'aetherspace/navigation'
3+
import * as UserInfoScreen from '../../screens/UserInfoScreen'
4+
5+
/* --- Config ---------------------------------------------------------------------------------- */
6+
7+
const ScreenComponent = UserInfoScreen.UserInfoScreen
8+
const screenConfig = UserInfoScreen.screenConfig
9+
10+
/* --- /me ------------------------------------------------------------------------------------- */
11+
12+
const PageScreen = (props: UserInfoScreen.UserInfoScreenProps) => (
13+
<AetherPage {...props} screen={ScreenComponent} screenConfig={screenConfig} />
14+
)
15+
16+
/* --- Exports --------------------------------------------------------------------------------- */
17+
18+
export const dynamic = screenConfig.dynamic
19+
20+
export default PageScreen

features/user-management/schemas/GetRequestContextArgs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { z, aetherSchema } from 'aetherspace/schemas'
33
/* --- Descriptions ---------------------------------------------------------------------------- */
44

55
const d = {
6-
echo: `Echoes back the input`,
6+
debug: `Returns extra debug info`,
77
}
88

99
/* --- GetRequestContextArgs ------------------------------------------------------------------ */
1010

1111
export const GetRequestContextArgs = aetherSchema('GetRequestContextArgs', {
12-
echo: z.string().nullish().describe(d.echo),
12+
debug: z.string().nullish().describe(d.debug),
1313
})
1414

1515
export type GetRequestContextArgs = z.infer<typeof GetRequestContextArgs>

features/user-management/screens/SignInScreen.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ export const SignInScreen = (props: SignInScreenProps) => {
3737
// Ignore if not loaded
3838
if (!isLoaded) return
3939

40-
console.log('handleSignInPressed()', { formState, isSignedIn })
41-
4240
try {
4341
// Remove the previous session if there is one
4442
if (isSignedIn) await session.end()
@@ -53,7 +51,7 @@ export const SignInScreen = (props: SignInScreenProps) => {
5351
await setActive({ session: completedSignIn.createdSessionId })
5452

5553
// Navigate to home
56-
openLink('/')
54+
openLink('/me')
5755
} catch (error) {
5856
console.error(JSON.stringify(error, null, 4))
5957
}

0 commit comments

Comments
 (0)