├── .env.sample
├── .eslintrc.json
├── .gitignore
├── README.md
├── components
└── clerk
│ ├── GithubLink.module.css
│ └── GithubLink.tsx
├── docs
└── cover.svg
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── _app.js
└── index.js
├── public
├── arrow-right.svg
├── clerk.svg
├── clerkandsupa.svg
├── favicon.ico
└── vercel.svg
└── styles
├── Home.module.css
└── globals.css
/.env.sample:
--------------------------------------------------------------------------------
1 | # Clerk env vars
2 | NEXT_PUBLIC_CLERK_FRONTEND_API=
3 | CLERK_API_KEY=
4 |
5 | # Supabase env vars
6 | NEXT_PUBLIC_SUPABASE_URL=
7 | NEXT_PUBLIC_SUPABASE_KEY=
8 | SUPABASE_JWT_SECRET=
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Next.js + Supabase + Clerk Todo App
2 |
3 | [](https://open.vscode.dev/clerkinc/clerk-supabase) [](https://docs.clerk.dev?utm_source=github&utm_medium=tutorial_repos&utm_campaign=supabase) [](https://twitter.com/intent/follow?screen_name=ClerkDev)
4 |
5 | This demo repo represents the final state of the Next.js todo app built with a Supabase database and Clerk for multifactor authentication. You can follow along with [this guide](https://clerk.dev/blog/nextjs-supabase-todos-with-multifactor-authentication?utm_source=github&utm_medium=tutorial_repos&utm_campaign=supabase) to build it yourself.
6 |
7 | 
8 |
9 | ## Contact
10 |
11 | If you need support or have anything you would like to ask, please reach out on our [Discord channel](https://discord.com/invite/b5rXHjAg7A). We'd love to chat!
12 |
13 |
--------------------------------------------------------------------------------
/components/clerk/GithubLink.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | font-family: "Inter", sans-serif;
3 | font-size: 0.75rem;
4 | font-weight: 400;
5 | line-height: 1rem;
6 | letter-spacing: 0em;
7 | text-align: left;
8 |
9 | display: flex;
10 |
11 | box-shadow: 0px 24px 48px 0px #00000029;
12 | border-radius: 6px;
13 |
14 | height: 40px;
15 | justify-content: space-between;
16 | align-items: center;
17 | padding: 0 1.5rem;
18 | background-color: white;
19 | }
20 |
21 | .logo {
22 | position: relative;
23 | top: 2px;
24 | min-width: 56px;
25 | }
26 |
27 | .label {
28 | font-family: "Inter", sans-serif;
29 | font-size: 0.75rem;
30 | font-weight: 400;
31 | line-height: 1rem;
32 | letter-spacing: 0em;
33 | text-align: left;
34 | margin: 0 1.5rem;
35 | }
36 |
37 | .rightLink {
38 | color: #335bf1;
39 | min-width: 100px;
40 | }
41 |
42 | .rightLink a:link {
43 | text-decoration: none;
44 | color: #335bf1;
45 | }
46 |
47 | .rightLink a:visited {
48 | text-decoration: none;
49 | color: #335bf1;
50 | }
51 |
52 | .rightLink a:hover {
53 | text-decoration: underline;
54 | color: #335bf1;
55 | }
56 |
57 | .rightLink a:active {
58 | text-decoration: underline;
59 | color: #335bf1;
60 | }
61 |
62 | .rightArrow {
63 | position: relative;
64 | top: 2px;
65 | left: 3px;
66 | }
67 |
--------------------------------------------------------------------------------
/components/clerk/GithubLink.tsx:
--------------------------------------------------------------------------------
1 | import styles from './GithubLink.module.css'
2 | import Image from 'next/image'
3 |
4 | type GithubLinkProps = {
5 | label: string
6 | repoLink: string
7 | }
8 | const GithubLink = (props: GithubLinkProps) => (
9 | <>
10 |
28 | >
29 | )
30 |
31 | export default GithubLink
32 |
--------------------------------------------------------------------------------
/docs/cover.svg:
--------------------------------------------------------------------------------
1 |
135 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "supabase-clerk",
3 | "private": true,
4 | "scripts": {
5 | "dev": "next dev",
6 | "build": "next build",
7 | "start": "next start",
8 | "lint": "next lint"
9 | },
10 | "dependencies": {
11 | "@clerk/nextjs": "^3.0.0-alpha.3",
12 | "@supabase/supabase-js": "^2.0.0",
13 | "jsonwebtoken": "^8.5.1",
14 | "next": "^12.0.9",
15 | "react": "17.0.2",
16 | "react-dom": "17.0.2"
17 | },
18 | "devDependencies": {
19 | "eslint": "8.4.1",
20 | "eslint-config-next": "12.0.7"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/globals.css'
2 | import GithubLink from '../components/clerk/GithubLink.tsx'
3 | import { ClerkProvider } from '@clerk/nextjs'
4 |
5 | function ClerkSupabaseApp({ Component, pageProps }) {
6 | return (
7 |
8 |
9 |
15 |
16 | )
17 | }
18 |
19 | export default ClerkSupabaseApp
20 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import styles from '../styles/Home.module.css'
2 | import { useState, useEffect } from 'react'
3 | import {
4 | useSession,
5 | useUser,
6 | UserButton,
7 | SignInButton,
8 | SignUpButton
9 | } from '@clerk/nextjs'
10 | import { createClient } from '@supabase/supabase-js'
11 |
12 | const supabaseClient = async supabaseAccessToken => {
13 | const supabase = createClient(
14 | process.env.NEXT_PUBLIC_SUPABASE_URL,
15 | process.env.NEXT_PUBLIC_SUPABASE_KEY,
16 | {
17 | global: { headers: { Authorization: `Bearer ${supabaseAccessToken}` } }
18 | }
19 | )
20 | // set Supabase JWT on the client object,
21 | // so it is sent up with all Supabase requests
22 | return supabase
23 | }
24 |
25 | export default function Home() {
26 | const { isSignedIn, isLoading, user } = useUser()
27 | const [todos, setTodos] = useState(null)
28 | return (
29 | <>
30 |
31 | {isLoading ? (
32 | <>>
33 | ) : (
34 |
35 |
36 | {isSignedIn ? (
37 | <>
38 |
Welcome {user.firstName}!
39 |
40 |
41 | >
42 | ) : (
43 |
44 | Sign in to create your todo list!
45 |
46 | )}
47 |
48 |
49 | )}
50 | >
51 | )
52 | }
53 |
54 | const Header = () => {
55 | const { isSignedIn } = useUser()
56 |
57 | return (
58 |
59 | My Todo App
60 | {isSignedIn ? (
61 |
62 | ) : (
63 |
64 |
65 |
66 |
67 |
68 | )}
69 |
70 | )
71 | }
72 |
73 | const TodoList = ({ todos, setTodos }) => {
74 | const { session } = useSession()
75 | const [loadingTodos, setLoadingTodos] = useState(true)
76 |
77 | // on first load, fetch and set todos
78 | useEffect(() => {
79 | const loadTodos = async () => {
80 | try {
81 | setLoadingTodos(true)
82 | const supabaseAccessToken = await session.getToken({
83 | template: 'Supabase'
84 | })
85 | const supabase = await supabaseClient(supabaseAccessToken)
86 | const { data: todos } = await supabase.from('todos').select('*')
87 | setTodos(todos)
88 | } catch (e) {
89 | alert(e)
90 | } finally {
91 | setLoadingTodos(false)
92 | }
93 | }
94 | loadTodos()
95 | }, [])
96 |
97 | // if loading, just show basic message
98 | if (loadingTodos) {
99 | return Loading...
100 | }
101 |
102 | // display all the todos
103 | return (
104 | <>
105 | {todos?.length > 0 ? (
106 |
107 |
108 | {todos.map(todo => (
109 | - {todo.title}
110 | ))}
111 |
112 |
113 | ) : (
114 | You don't have any todos!
115 | )}
116 | >
117 | )
118 | }
119 |
120 | function AddTodoForm({ todos, setTodos }) {
121 | const { session } = useSession()
122 | const [newTodo, setNewTodo] = useState('')
123 | const handleSubmit = async e => {
124 | e.preventDefault()
125 | if (newTodo === '') {
126 | return
127 | }
128 |
129 | const supabaseAccessToken = await session.getToken({
130 | template: 'Supabase'
131 | })
132 | const supabase = await supabaseClient(supabaseAccessToken)
133 | const { data } = await supabase
134 | .from('todos')
135 | .insert({ title: newTodo, user_id: session.user.id })
136 | .select()
137 |
138 | setTodos([...todos, data[0]])
139 | setNewTodo('')
140 | }
141 |
142 | return (
143 |
147 | )
148 | }
149 |
--------------------------------------------------------------------------------
/public/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/public/clerk.svg:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/public/clerkandsupa.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/clerk/clerk-supabase/a6bf62c35aa996ed7b6308ea1788a503f0783d24/public/favicon.ico
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .header {
2 | padding: 1rem 2rem;
3 | height: 4rem;
4 | background-color: lightgray;
5 | width: 100%;
6 | display: flex;
7 | justify-content: space-between;
8 | align-items: center;
9 | }
10 |
11 | .main {
12 | margin: auto;
13 | max-width: 400px;
14 | }
15 |
16 | .label {
17 | padding: 1rem 2rem;
18 | }
19 |
20 | .container {
21 | padding: 1rem 2rem;
22 | display: flex;
23 | flex-direction: column;
24 | align-items: center;
25 | }
26 |
27 | .todoList {
28 | padding-left: 1rem;
29 | align-self: baseline;
30 | }
31 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
18 | footer {
19 | position: fixed;
20 | left: 0;
21 | right: 0;
22 | bottom: 0;
23 | display: flex;
24 | align-items: center;
25 | justify-content: center;
26 | margin: 20px;
27 | }
28 |
--------------------------------------------------------------------------------