├── .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 |
10 |
--------------------------------------------------------------------------------
/public/images/astro-logo-dark.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/public/images/clerk.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
6 |
7 |
8 |
9 |
27 |
--------------------------------------------------------------------------------
/src/components/auth/SignedOut.astro:
--------------------------------------------------------------------------------
1 | ---
2 | ---
3 |
4 |
5 |
6 |
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 | Pending count is not available on org object in node sdk
47 |
48 |
- Image
49 |
-
50 |
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 |
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 |
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 |
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 |
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 | <-- Pending count is not available on org object in node sdk -->
191 |
192 |
- Image
193 |
-
194 |
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 |
12 |
13 |
14 | Auth starts here.
15 |
16 |
17 | A simple and powerful Astro template featuring authentication and
18 | user management powered by Clerk.
19 |
20 |
73 |
74 |
75 |
76 |
77 |
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 | }
--------------------------------------------------------------------------------