├── .env.sample ├── .gitignore ├── .vscode ├── extensions.json └── launch.json ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public ├── favicon.svg └── images │ ├── astro-logo-dark.svg │ ├── clerk.svg │ └── components.svg ├── src ├── components │ ├── Layout.astro │ ├── auth │ │ ├── OrganizationSwitcher.astro │ │ ├── SignInButton.astro │ │ ├── SignedIn.astro │ │ ├── SignedOut.astro │ │ └── UserButton.astro │ └── user │ │ ├── astro │ │ ├── OrgDetails.astro │ │ ├── SessionDetails.astro │ │ ├── UserDetails.astro │ │ └── UserGreeting.astro │ │ └── react │ │ ├── OrgDetails.tsx │ │ ├── SessionDetails.tsx │ │ ├── UserDetails.tsx │ │ └── UserGreeting.tsx ├── env.d.ts ├── lib │ ├── authStore.ts │ └── clerk.ts ├── middleware │ ├── index.ts │ ├── protectedApis.ts │ └── protectedPages.ts └── pages │ ├── api │ └── auth │ │ └── index.ts │ ├── dashboard │ ├── astro │ │ └── index.astro │ ├── htmx │ │ └── index.astro │ ├── index.astro │ └── react │ │ └── index.astro │ └── index.astro ├── tailwind.config.mjs └── tsconfig.json /.env.sample: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | PUBLIC_CLERK_PUBLISHABLE_KEY= 3 | CLERK_SECRET_KEY= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | .vercel/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | 17 | # environment variables 18 | .env 19 | .env.production 20 | 21 | # macOS-specific files 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *** 2 | 3 | # NOT MAINTAINED 4 | 5 | [maybrito](https://github.com/maykbrito/astro-clerk-auth) has an updated and maintained version of the astro and clerk implementaiton 6 | 7 | *** 8 | 9 | # Clerk and Astro SSR Template 10 | 11 | ## Introduction 12 | 13 | This project is based on [Astro 4.0](https://astro.build) blank template and pulls in [Clerk](https://clerk.com) authentication via their vanilla js client side and backend libraries. This template is a clone of the [Next.js and Clerk](https://github.com/clerk/clerk-nextjs-demo-app-router/) demo project. 14 | 15 | The dashboard is currently using react components to render the user, session and organization information. As noted in the [TO DO List](#to-do-list) I plan to add a few additional variations of the dashboard page. 16 | 17 | ### View it live 18 | 19 | On Vercel at: [https://clerk-astro-demo-vin-e.vercel.app/](https://clerk-astro-demo-vin-e.vercel.app/) 20 | 21 | ## NOTE TO CLERK 22 | 23 | Please feel free to take this code and publish/modify on your github channel. 24 | 25 | ## Known issue(s) 26 | 27 | * At the moment this implementation does not support ViewTransitions (or HTMX boost). The pages are expected to be fully rendered and the javascript code to watch for clerk and clerk.isReady() does not execute. 28 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config' 2 | import tailwind from '@astrojs/tailwind' 3 | import react from '@astrojs/react' 4 | import htmx from 'astro-htmx' 5 | import vercel from '@astrojs/vercel/serverless' 6 | 7 | // https://astro.build/config 8 | export default defineConfig({ 9 | output: 'server', 10 | integrations: [tailwind(), react(), htmx()], 11 | adapter: vercel() 12 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clerk-astro-demo", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/check": "^0.3.2", 14 | "@astrojs/react": "^3.0.7", 15 | "@astrojs/tailwind": "^5.0.3", 16 | "@astrojs/vercel": "^6.1.0", 17 | "@clerk/clerk-js": "^4.67.0", 18 | "@clerk/clerk-sdk-node": "^4.13.2", 19 | "@nanostores/react": "^0.7.1", 20 | "@types/react": "^18.2.45", 21 | "@types/react-dom": "^18.2.17", 22 | "astro": "^4.0.5", 23 | "astro-htmx": "^1.0.3", 24 | "htmx.org": "^1.9.9", 25 | "nanostores": "^0.9.5", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "tailwindcss": "^3.3.6", 29 | "typescript": "^5.3.3" 30 | }, 31 | "overrides": { 32 | "astro-htmx": { 33 | "astro": "$astro" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/astro-logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/clerk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { Image } from 'astro:assets' 3 | import OrganizationSwitcher from './auth/OrganizationSwitcher.astro' 4 | import SignedIn from './auth/SignedIn.astro' 5 | import UserButton from './auth/UserButton.astro' 6 | --- 7 | 8 | 9 | 10 |
13 | 14 | Clerk logo 20 | 27 | 33 | 34 | Astro Logo 40 | 41 |
42 | 43 | 46 |
47 | 48 |
49 | 50 |
51 | 52 |
53 |
54 | 55 |
56 | 166 | 167 | 171 | -------------------------------------------------------------------------------- /src/components/auth/OrganizationSwitcher.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { isSmall = false } = Astro.props; 3 | --- 4 | 5 |
6 | 7 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/auth/SignInButton.astro: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | --- 4 | 5 | 29 | 30 | 47 | -------------------------------------------------------------------------------- /src/components/auth/SignedIn.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { classes } = Astro.props 3 | --- 4 | 5 | 8 | 9 | 27 | -------------------------------------------------------------------------------- /src/components/auth/SignedOut.astro: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/auth/UserButton.astro: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 |
5 | 6 | -------------------------------------------------------------------------------- /src/components/user/astro/OrgDetails.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { org } = Astro.props; 3 | --- 4 | 5 |
9 |
10 |

11 | Organization 12 |

13 |
14 | { 15 | !org && ( 16 |
17 | You are currently logged in to your personal workspace. 18 |
19 | Create or switch to an organization to see its details. 20 |
21 | ) 22 | } 23 | 24 | { 25 | org && ( 26 |
27 |
28 |
29 |
Organization ID
30 |
31 | {org.id} 32 |
33 |
34 |
35 |
Name
36 |
37 | {org.name} 38 |
39 |
40 |
41 |
Members
42 |
43 | {org.members_count || 0} 44 |
45 |
46 | 47 |
48 |
Image
49 |
50 | {`Logo 57 |
58 |
59 |
60 |
61 | ) 62 | } 63 |
64 | -------------------------------------------------------------------------------- /src/components/user/astro/SessionDetails.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { session } = Astro.props 3 | --- 4 | 5 |
9 |
10 |

11 | Session 12 |

13 |
14 |
15 |
16 |
17 |
Session ID
18 |
19 | {session.id} 20 |
21 |
22 |
23 |
Status
24 |
25 | {session.status === `active` && 26 | 27 |
28 | 35 | 36 | 37 |
38 | Active 39 |
40 | } 41 |
42 |
43 |
44 |
Last Active
45 |
46 | {new Date(session.lastActiveAt).toLocaleString()} 47 |
48 |
49 |
50 |
Expiry
51 |
52 | {new Date(session.expireAt).toLocaleString()} 53 |
54 |
55 |
56 |
57 |
-------------------------------------------------------------------------------- /src/components/user/astro/UserDetails.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { user } = Astro.props 3 | --- 4 | 5 |
9 |
10 |

User

11 |
12 |
13 |
14 |
15 |
User ID
16 |
17 | {user.id} 18 |
19 |
20 | { 21 | user.firstName 22 | ? 23 |
24 |
First Name
25 |
26 | {user.firstName} 27 |
28 |
29 | 30 | : null 31 | } 32 | { 33 | user.lastName 34 | ? 35 |
36 |
Last Name
37 |
38 | {user.lastName} 39 |
40 |
41 | : null 42 | } 43 |
44 |
Email addresses
45 |
46 | { 47 | user.emailAddresses.map( 48 | (email: any) => 49 |
50 | {email.emailAddress} 51 | { 52 | user.primaryEmailAddressId === email.id && 53 | 54 | Primary 55 | 56 | } 57 |
58 | ) 59 | } 60 |
61 |
62 | { 63 | user.imageUrl 64 | ? 65 |
66 |
Profile Image
67 |
68 | 72 |
73 |
74 | : null 75 | } 76 |
77 |
78 |
79 | -------------------------------------------------------------------------------- /src/components/user/astro/UserGreeting.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { name } = Astro.props 3 | --- 4 | 5 |

6 | 👋 Hi, {name || `Stranger`} 7 |

8 | -------------------------------------------------------------------------------- /src/components/user/react/OrgDetails.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { useStore } from '@nanostores/react' 3 | import { auth } from '../../../lib/authStore' 4 | 5 | export function OrgDetails() { 6 | const $clerk = useStore(auth) 7 | const [organization, setOrganization] = useState($clerk?.organization) 8 | 9 | $clerk?.addListener((clerk) => { 10 | if (organization?.id !== clerk.organization?.id) { 11 | setOrganization(clerk.organization) 12 | } 13 | }) 14 | 15 | return ( 16 |
22 |
23 |

24 | Organization 25 |

26 |
27 | {$clerk?.isReady() ? ( 28 | organization ? ( 29 |
30 |
31 |
32 |
Organization ID
33 |
34 | {organization.id} 35 |
36 |
37 |
38 |
Name
39 |
40 | {organization.name} 41 |
42 |
43 |
44 |
Members
45 |
46 | {organization.membersCount} 47 |
48 |
49 |
50 |
51 | Pending invitations 52 |
53 |
54 | {organization.pendingInvitationsCount} 55 |
56 |
57 |
58 |
Image
59 |
60 | {`Logo 67 |
68 |
69 |
70 |
71 | ) : ( 72 |
73 | You are currently logged in to your personal workspace. 74 |
75 | Create or switch to an organization to see its details. 76 |
77 | )) : ( 78 |
79 | Loading organization data... 80 |
81 | )} 82 |
83 | ); 84 | } -------------------------------------------------------------------------------- /src/components/user/react/SessionDetails.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@nanostores/react' 2 | import { auth } from '../../../lib/authStore' 3 | 4 | export function SessionDetails() { 5 | const $clerk = useStore(auth) 6 | const session = $clerk?.session 7 | 8 | return ( 9 |
15 |
16 |

17 | Session 18 |

19 |
20 | {$clerk?.isReady() && session ? ( 21 |
22 |
23 |
24 |
Session ID
25 |
26 | {session.id} 27 |
28 |
29 |
30 |
Status
31 |
32 | {session.status === `active` && ( 33 | 34 |
35 | 42 | 43 | 44 |
45 | Active 46 |
47 | )} 48 |
49 |
50 |
51 |
Last Active
52 |
53 | {session.lastActiveAt.toLocaleString()} 54 |
55 |
56 |
57 |
Expiry
58 |
59 | {session.expireAt.toLocaleString()} 60 |
61 |
62 |
63 |
64 | ) : ( 65 |
66 | Loading user data... 67 |
68 | )} 69 |
70 | ); 71 | } -------------------------------------------------------------------------------- /src/components/user/react/UserDetails.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@nanostores/react' 2 | import { auth } from '../../../lib/authStore' 3 | 4 | export function UserDetails() { 5 | const $clerk = useStore(auth) 6 | const user = $clerk?.user 7 | 8 | return ( 9 |
15 |
16 |

17 | User 18 |

19 |
20 | {$clerk?.isReady() && user ? ( 21 |
22 |
23 |
24 |
User ID
25 |
26 | {user.id} 27 |
28 |
29 | {user.firstName && ( 30 |
31 |
First Name
32 |
33 | {user.firstName} 34 |
35 |
36 | )} 37 | {user.lastName && ( 38 |
39 |
Last Name
40 |
41 | {user.lastName} 42 |
43 |
44 | )} 45 |
46 |
Email addresses
47 |
48 | {user.emailAddresses.map((email: any) => ( 49 |
50 | {email.emailAddress} 51 | {user.primaryEmailAddressId === email.id && ( 52 | 53 | Primary 54 | 55 | )} 56 |
57 | ))} 58 |
59 |
60 | {user.imageUrl && ( 61 |
62 |
Profile Image
63 |
64 | 68 |
69 |
70 | )} 71 |
72 |
73 | ) : ( 74 |
75 | Loading user data... 76 |
77 | )} 78 |
79 | ) 80 | } -------------------------------------------------------------------------------- /src/components/user/react/UserGreeting.tsx: -------------------------------------------------------------------------------- 1 | import { useStore } from '@nanostores/react' 2 | import { auth } from '../../../lib/authStore' 3 | 4 | export function UserGreeting() { 5 | const $clerk = useStore(auth) 6 | const user = $clerk?.user 7 | return ( 8 |

9 | 👋 Hi, {user?.firstName || `Stranger`} 10 |

11 | ) 12 | } -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | PUBLIC_CLERK_PUBLISHABLE_KEY: string 5 | CLERK_SECRET_KEY: string 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv 10 | } -------------------------------------------------------------------------------- /src/lib/authStore.ts: -------------------------------------------------------------------------------- 1 | import type Clerk from '@clerk/clerk-js' 2 | import { atom } from 'nanostores' 3 | 4 | export const auth = atom(null) -------------------------------------------------------------------------------- /src/lib/clerk.ts: -------------------------------------------------------------------------------- 1 | import Clerk from '@clerk/clerk-js' 2 | import { auth } from './authStore' 3 | 4 | const clerkPublishableKey = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY 5 | let clerk: Clerk 6 | 7 | export const initializeClerk = () => { 8 | const authNano = auth.get() 9 | if (authNano) return 10 | 11 | clerk = new Clerk(clerkPublishableKey) 12 | clerk 13 | .load() 14 | .then(() => { 15 | auth.set(clerk) 16 | }) 17 | .catch(error => console.error(error)) 18 | } 19 | -------------------------------------------------------------------------------- /src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import { sequence } from 'astro:middleware' 2 | import { protectedApis } from './protectedApis' 3 | import { protectedPages } from './protectedPages' 4 | 5 | export const onRequest = sequence(protectedPages, protectedApis) -------------------------------------------------------------------------------- /src/middleware/protectedApis.ts: -------------------------------------------------------------------------------- 1 | import { defineMiddleware } from 'astro:middleware' 2 | import clerkClient from '@clerk/clerk-sdk-node' 3 | 4 | const publishableKey = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY 5 | const secretKey = import.meta.env.CLERK_SECRET_KEY 6 | 7 | const protectedApiUrls = ['/api/auth'] 8 | export const protectedApis = defineMiddleware(async ({request}, next) => { 9 | const url = new URL(request.url) 10 | if (!protectedApiUrls.some(path => url.pathname.startsWith(path))) { 11 | return next() 12 | } 13 | 14 | const { isSignedIn } = await clerkClient.authenticateRequest({ request, publishableKey, secretKey }) 15 | if (!isSignedIn) { 16 | return new Response('

Bad Request

', { status: 401}) 17 | } 18 | 19 | return next() 20 | }) -------------------------------------------------------------------------------- /src/middleware/protectedPages.ts: -------------------------------------------------------------------------------- 1 | import { defineMiddleware } from 'astro:middleware' 2 | import clerkClient from '@clerk/clerk-sdk-node' 3 | 4 | const publishableKey = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY 5 | const secretKey = import.meta.env.CLERK_SECRET_KEY 6 | 7 | const protectedPageUrls = ['/dashboard'] 8 | export const protectedPages = defineMiddleware(async ({request, redirect}, next) => { 9 | const url = new URL(request.url) 10 | if (!protectedPageUrls.some(path => url.pathname.startsWith(path))) { 11 | return next() 12 | } 13 | 14 | const { isSignedIn } = await clerkClient.authenticateRequest({ request, publishableKey, secretKey }) 15 | if (!isSignedIn) { 16 | return redirect('/') 17 | } 18 | 19 | // return a Response or the result of calling `next()` 20 | return next() 21 | }) -------------------------------------------------------------------------------- /src/pages/api/auth/index.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from 'astro' 2 | import { createClerkClient, type Organization, type Session, type User } from '@clerk/clerk-sdk-node' 3 | 4 | const publishableKey = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY 5 | const secretKey = import.meta.env.CLERK_SECRET_KEY 6 | 7 | const clerk = createClerkClient({ publishableKey, secretKey }) 8 | export const GET: APIRoute = async ({request}) => { 9 | const { toAuth } = await clerk.authenticateRequest({ request, publishableKey, secretKey }) 10 | const auth = toAuth() 11 | const user = await clerk.users.getUser(auth!.userId!) 12 | const session = await clerk.sessions.getSession(auth!.sessionId!) 13 | let org = auth?.orgId ? await clerk.organizations.getOrganization({ organizationId: auth.orgId }) : undefined 14 | 15 | let content = getUserGreeting(user) 16 | content += `
` 17 | content += getUserDetails(user) 18 | content += getSessionDetails(session!) 19 | content += getOrgDetails(org) 20 | content += `
` 21 | 22 | return new Response(content, { 23 | status: 200 24 | }) 25 | } 26 | 27 | const getUserGreeting = (user: User) => { 28 | return `

👋 Hi, ${user?.firstName || 'Stranger'}

` 29 | } 30 | 31 | const getUserDetails = (user: User) => { 32 | return ` 33 |
37 |
38 |

39 | User 40 |

41 |
42 |
43 |
44 |
45 |
User ID
46 |
47 | ${user.id} 48 |
49 |
50 | ${user.firstName ? ` 51 |
52 |
First Name
53 |
54 | ${user.firstName} 55 |
56 |
57 | `: ''} 58 | ${user.lastName ? ` 59 |
60 |
Last Name
61 |
62 | ${user.lastName} 63 |
64 |
65 | `: ''} 66 |
67 |
Email addresses
68 |
69 | ${user.emailAddresses.map((email: any) => (` 70 |
71 | ${email.emailAddress} 72 | ${user.primaryEmailAddressId === email.id && ` 73 | 74 | Primary 75 | 76 | `} 77 |
78 | `))} 79 |
80 |
81 | ${user.imageUrl ? ` 82 |
83 |
Profile Image
84 |
85 | 89 |
90 |
91 | ` : ''} 92 |
93 |
94 |
95 | ` 96 | } 97 | 98 | const getSessionDetails = (session: Session) => { 99 | return ` 100 |
104 |
105 |

106 | Session 107 |

108 |
109 |
110 |
111 |
112 |
Session ID
113 |
114 | ${session.id} 115 |
116 |
117 |
118 |
Status
119 |
120 | ${session.status === `active` && ` 121 | 122 |
123 | 130 | 131 | 132 |
133 | Active 134 |
135 | `} 136 |
137 |
138 |
139 |
Last Active
140 |
141 | ${new Date(session.lastActiveAt).toLocaleString()} 142 |
143 |
144 |
145 |
Expiry
146 |
147 | ${new Date(session.expireAt).toLocaleString()} 148 |
149 |
150 |
151 |
152 |
153 | ` 154 | } 155 | 156 | const getOrgDetails = (org?: Organization) => { 157 | let content = ` 158 |
162 |
163 |

164 | Organization 165 |

166 |
167 | ` 168 | if (org) { 169 | content += ` 170 |
171 |
172 |
173 |
Organization ID
174 |
175 | ${org.id} 176 |
177 |
178 |
179 |
Name
180 |
181 | ${org.name} 182 |
183 |
184 |
185 |
Members
186 |
187 | ${org.members_count || 0} 188 |
189 |
190 | 191 |
192 |
Image
193 |
194 | Logo for ${org.name} 201 |
202 |
203 |
204 |
205 | ` 206 | } else { 207 | content += ` 208 |
209 | You are currently logged in to your personal workspace. 210 |
211 | Create or switch to an organization to see its details. 212 |
213 | ` 214 | } 215 | 216 | content += '
' 217 | return content 218 | } -------------------------------------------------------------------------------- /src/pages/dashboard/astro/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../../../components/Layout.astro' 3 | import UserGreeting from '../../../components/user/astro/UserGreeting.astro' 4 | import UserDetails from '../../../components/user/astro/UserDetails.astro' 5 | import SessionDetails from '../../../components/user/astro/SessionDetails.astro' 6 | import OrgDetails from '../../../components/user/astro/OrgDetails.astro' 7 | 8 | import { createClerkClient } from '@clerk/clerk-sdk-node' 9 | const publishableKey = import.meta.env.PUBLIC_CLERK_PUBLISHABLE_KEY 10 | const secretKey = import.meta.env.CLERK_SECRET_KEY 11 | 12 | const request = Astro.request 13 | const clerk = createClerkClient({ publishableKey, secretKey }) 14 | const { toAuth } = await clerk.authenticateRequest({ request, publishableKey, secretKey }) 15 | const auth = toAuth() 16 | const user = await clerk.users.getUser(auth!.userId!) 17 | const session = await clerk.sessions.getSession(auth!.sessionId!) 18 | const org = auth?.orgId ? await clerk.organizations.getOrganization({ organizationId: auth.orgId }) : undefined 19 | --- 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | 28 |
29 |

30 | What's next? 31 |

32 | Read the{" "} 33 | 38 | Clerk Docs -> 39 | 40 |

You are viewing

41 | The Astro component version of this page.{" "} 42 | 43 |
44 | View the React version 45 |
46 | View the HTMX version 47 |
48 |
49 | 50 | -------------------------------------------------------------------------------- /src/pages/dashboard/htmx/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../../../components/Layout.astro' 3 | --- 4 | 5 | 6 |
7 |
8 |
9 |
13 | Loading user data... 14 |
15 |
19 | Loading session data... 20 |
21 |
25 | Loading organization data... 26 |
27 |
28 |
29 |

30 | What's next? 31 |

32 | Read the{" "} 33 | 38 | Clerk Docs -> 39 | 40 |

You are viewing

41 | The HTMX component version of this page.{" "} 42 |
43 | View the React version 44 |
45 | View the Astro version 46 |
47 |
48 | 49 | -------------------------------------------------------------------------------- /src/pages/dashboard/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import AstroPage from './astro/index.astro' 3 | 4 | /** 5 | * Defaulting this page to the astro version. 6 | */ 7 | --- 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/dashboard/react/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../../../components/Layout.astro' 3 | import { OrgDetails } from '../../../components/user/react/OrgDetails' 4 | import { SessionDetails } from '../../../components/user/react/SessionDetails' 5 | import { UserDetails } from '../../../components/user/react/UserDetails' 6 | import { UserGreeting } from '../../../components/user/react/UserGreeting' 7 | 8 | // TODO: Add UTM params 9 | // ?utm_source=vercel-template&utm_medium=template_repos&utm_campaign=nextjs_template 10 | --- 11 | 12 |
13 | 14 |
15 | 16 | 17 | 18 |
19 |

20 | What's next? 21 |

22 | Read the{" "} 23 | 28 | Clerk Docs -> 29 | 30 |

You are viewing

31 | The React component version of this page.{" "} 32 |
33 | View the HTMX version 34 |
35 | View the Astro version 36 |
37 |
-------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '../components/Layout.astro' 3 | import { Image } from 'astro:assets' 4 | import componentImage from '../../public/images/components.svg' 5 | import SignedIn from '../components/auth/SignedIn.astro' 6 | import SignedOut from '../components/auth/SignedOut.astro' 7 | import SignInButton from '../components/auth/SignInButton.astro' 8 | --- 9 | 10 | 11 | 78 | 79 | -------------------------------------------------------------------------------- /tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], 4 | theme: { 5 | extend: { 6 | colors: { 7 | "primary-600": "#6C47FF", 8 | "primary-700": "#5639CC", 9 | "primary-50": "#F4F2FF", 10 | "success-700": "#027A48", 11 | "success-50": "#ECFDF3", 12 | }, 13 | }, 14 | }, 15 | plugins: [], 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "jsx": "react-jsx", 5 | "jsxImportSource": "react" 6 | } 7 | } --------------------------------------------------------------------------------