├── src ├── vite-env.d.ts ├── supabase │ └── index.ts ├── pages │ ├── LoadingPage.tsx │ ├── 404Page.tsx │ ├── ProtectedPage.tsx │ ├── auth │ │ ├── SignInPage.tsx │ │ └── SignUpPage.tsx │ └── HomePage.tsx ├── Providers.tsx ├── main.tsx ├── router │ ├── AuthProtectedRoute.tsx │ └── index.tsx ├── config.ts ├── context │ └── SessionContext.tsx ├── index.css └── assets │ └── react.svg ├── .env.example ├── vercel.json ├── remove_me.png ├── .github └── dependabot.yml ├── vite.config.ts ├── tsconfig.node.json ├── .gitignore ├── index.html ├── .eslintrc.cjs ├── tsconfig.json ├── package.json ├── LICENSE.MD ├── public └── vite.svg └── README.md /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | VITE_SUPABASE_URL= 2 | VITE_SUPABASE_ANON_KEY= 3 | 4 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/(.*)", "destination": "/" }] 3 | } 4 | -------------------------------------------------------------------------------- /remove_me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmvergara/react-supabase-auth-template/HEAD/remove_me.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | versioning-strategy: "increase" 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/supabase/index.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@supabase/supabase-js"; 2 | import { SUPABASE_ANON_KEY, SUPABASE_URL } from "../config"; 3 | 4 | const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); 5 | 6 | export default supabase; 7 | -------------------------------------------------------------------------------- /src/pages/LoadingPage.tsx: -------------------------------------------------------------------------------- 1 | const LoadingPage = () => { 2 | return ( 3 |
4 |
5 |

Loading...

6 |
7 |
8 | ); 9 | }; 10 | 11 | export default LoadingPage; 12 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /src/Providers.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import { SessionProvider } from "./context/SessionContext"; 3 | 4 | const Providers = () => { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | 12 | export default Providers; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .env 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import router from "./router"; 5 | import { RouterProvider } from "react-router-dom"; 6 | 7 | ReactDOM.createRoot(document.getElementById("root")!).render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /src/pages/404Page.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | const NotFoundPage: React.FC = () => { 4 | return ( 5 |
6 |
7 |

404 Page Not Found

8 | Go back to Home 9 |
10 |
11 | ); 12 | }; 13 | 14 | export default NotFoundPage; 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Supabase Auth Template 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/router/AuthProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import NotFoundPage from "../pages/404Page"; 3 | import { useSession } from "../context/SessionContext"; 4 | 5 | const AuthProtectedRoute = () => { 6 | const { session } = useSession(); 7 | if (!session) { 8 | // or you can redirect to a different page and show a message 9 | return ; 10 | } 11 | return ; 12 | }; 13 | 14 | export default AuthProtectedRoute; 15 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | if (!import.meta.env.VITE_SUPABASE_ANON_KEY) { 2 | alert("VITE_SUPABASE_ANON_KEY is required"); 3 | throw new Error("VITE_SUPABASE_ANON_KEY is required"); 4 | } 5 | if (!import.meta.env.VITE_SUPABASE_URL) { 6 | alert("VITE_SUPABASE_URL is required"); 7 | throw new Error("VITE_SUPABASE_URL is required"); 8 | } 9 | 10 | console.log(import.meta.env.VITE_SUPABASE_ANON_KEY); 11 | console.log(import.meta.env.VITE_SUPABASE_URL); 12 | export const SUPABASE_ANON_KEY = import.meta.env.VITE_SUPABASE_ANON_KEY; 13 | export const SUPABASE_URL = import.meta.env.VITE_SUPABASE_URL; 14 | -------------------------------------------------------------------------------- /src/pages/ProtectedPage.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import { useSession } from "../context/SessionContext"; 3 | 4 | const ProtectedPage = () => { 5 | const { session } = useSession(); 6 | return ( 7 |
8 | 9 | ◄ Home 10 | 11 |
12 |

This is a Protected Page

13 |

Current User : {session?.user.email || "None"}

14 |
15 |
16 | ); 17 | }; 18 | 19 | export default ProtectedPage; 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-supabase-auth-template", 3 | "author": "mmvergara", 4 | "license": "MIT", 5 | "private": true, 6 | "version": "0.0.0", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "vite", 10 | "build": "tsc && vite build", 11 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 12 | "preview": "vite preview" 13 | }, 14 | "dependencies": { 15 | "@supabase/supabase-js": "^2.49.4", 16 | "react": "^19.0.0", 17 | "react-dom": "^19.1.0", 18 | "react-router-dom": "^7.6.2" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^19.0.7", 22 | "@types/react-dom": "^19.1.2", 23 | "@typescript-eslint/eslint-plugin": "^7.2.0", 24 | "@typescript-eslint/parser": "^7.2.0", 25 | "@vitejs/plugin-react": "^4.2.1", 26 | "eslint": "^8.57.0", 27 | "eslint-plugin-react-hooks": "^5.2.0", 28 | "eslint-plugin-react-refresh": "^0.4.18", 29 | "typescript": "^5.7.3", 30 | "vite": "^6.2.6" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 MARK MATTHEW VERGARA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/context/SessionContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useState } from "react"; 2 | import supabase from "../supabase"; 3 | import LoadingPage from "../pages/LoadingPage"; 4 | import { Session } from "@supabase/supabase-js"; 5 | 6 | const SessionContext = createContext<{ 7 | session: Session | null; 8 | }>({ 9 | session: null, 10 | }); 11 | 12 | export const useSession = () => { 13 | const context = useContext(SessionContext); 14 | if (!context) { 15 | throw new Error("useSession must be used within a SessionProvider"); 16 | } 17 | return context; 18 | }; 19 | 20 | type Props = { children: React.ReactNode }; 21 | export const SessionProvider = ({ children }: Props) => { 22 | const [session, setSession] = useState(null); 23 | const [isLoading, setIsLoading] = useState(true); 24 | 25 | useEffect(() => { 26 | const authStateListener = supabase.auth.onAuthStateChange( 27 | async (_, session) => { 28 | setSession(session); 29 | setIsLoading(false); 30 | } 31 | ); 32 | 33 | return () => { 34 | authStateListener.data.subscription.unsubscribe(); 35 | }; 36 | }, [supabase]); 37 | 38 | return ( 39 | 40 | {isLoading ? : children} 41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/router/index.tsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter } from "react-router-dom"; 2 | import HomePage from "../pages/HomePage.tsx"; 3 | import SignInPage from "../pages/auth/SignInPage.tsx"; 4 | import SignUpPage from "../pages/auth/SignUpPage.tsx"; 5 | import ProtectedPage from "../pages/ProtectedPage.tsx"; 6 | import NotFoundPage from "../pages/404Page.tsx"; 7 | import AuthProtectedRoute from "./AuthProtectedRoute.tsx"; 8 | import Providers from "../Providers.tsx"; 9 | 10 | const router = createBrowserRouter([ 11 | // I recommend you reflect the routes here in the pages folder 12 | { 13 | path: "/", 14 | element: , 15 | children: [ 16 | // Public routes 17 | { 18 | path: "/", 19 | element: , 20 | }, 21 | { 22 | path: "/auth/sign-in", 23 | element: , 24 | }, 25 | { 26 | path: "/auth/sign-up", 27 | element: , 28 | }, 29 | // Auth Protected routes 30 | { 31 | path: "/", 32 | element: , 33 | children: [ 34 | { 35 | path: "/protected", 36 | element: , 37 | }, 38 | ], 39 | }, 40 | ], 41 | }, 42 | { 43 | path: "*", 44 | element: , 45 | }, 46 | ]); 47 | 48 | export default router; 49 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/auth/SignInPage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Link, Navigate } from "react-router-dom"; 3 | import { useSession } from "../../context/SessionContext"; 4 | import supabase from "../../supabase"; 5 | 6 | const SignInPage = () => { 7 | // ============================== 8 | // If user is already logged in, redirect to home 9 | // This logic is being repeated in SignIn and SignUp.. 10 | const { session } = useSession(); 11 | if (session) return ; 12 | // maybe we can create a wrapper component for these pages 13 | // just like the ./router/AuthProtectedRoute.tsx? up to you. 14 | // ============================== 15 | const [status, setStatus] = useState(""); 16 | const [formValues, setFormValues] = useState({ 17 | email: "", 18 | password: "", 19 | }); 20 | 21 | const handleInputChange = (e: React.ChangeEvent) => { 22 | setFormValues({ ...formValues, [e.target.name]: e.target.value }); 23 | }; 24 | 25 | const handleSubmit = async (e: React.FormEvent) => { 26 | e.preventDefault(); 27 | setStatus("Logging in..."); 28 | const { error } = await supabase.auth.signInWithPassword({ 29 | email: formValues.email, 30 | password: formValues.password, 31 | }); 32 | if (error) { 33 | alert(error.message); 34 | } 35 | setStatus(""); 36 | }; 37 | return ( 38 |
39 | 40 | ◄ Home 41 | 42 |
43 |

Sign In

44 | 50 | 56 | 57 | 58 | Don't have an account? Sign Up 59 | 60 | {status &&

{status}

} 61 |
62 |
63 | ); 64 | }; 65 | 66 | export default SignInPage; 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

React Supabase Auth with Protected Routes

3 |

4 | 5 |

6 | 7 |

8 | 9 | [**`🌐 App Demo`**](https://react-supabase-auth-template.vercel.app/) 10 | 11 | ## Features 12 | 13 | - 🚀 Protected Routes 14 | - 🚀 Supabase Session Object in Global Context via `useSession` 15 | - 🚀 User Authentication 16 | - 🚀 Routing and Route Guards 17 | 18 | It's also blazingly fast 🔥 No really, [try it out for yourself.](https://react-supabase-auth-template.vercel.app/) 19 | 20 | [We also have a similar template for FIREBASE 🔥](https://github.com/mmvergara/react-firebase-auth-template) 21 | ## Getting Started 22 | 23 | 1. Clone the repository 24 | 2. Install dependencies: `npm install` 25 | 3. Create `.env` using the `.env.example` as a template 26 | ``` 27 | VITE_SUPABASE_URL= 28 | VITE_SUPABASE_ANON_KEY= 29 | ``` 30 | 4. Run the app: `npm run dev` 31 | 32 | ## What you need to know 33 | 34 | - `/router/index.tsx` is where you declare your routes 35 | - `/context/SessionContext.tsx` is where you can find the `useSession` hook 36 | - This hook gives you access to the `session` object from Supabase globally 37 | - `/Providers.tsx` is where you can add more `providers` or `wrappers` 38 | 39 | ## Other Supabase Templates 40 | 41 | - [React ShadCN Supabase Auth Template 🌟](https://github.com/mmvergara/react-supabase-shadcn-auth-template) 42 | - [NextJs ShadCN Supabase Auth Template 🌟](https://github.com/mmvergara/nextjs-shadcn-supabase-auth-starter) 43 | 44 | ## More Starter Templates 45 | 46 | - [NextJs MongoDB Prisma Auth Template 🌟](https://github.com/mmvergara/nextjs-mongodb-prisma-auth-template) 47 | - [NextJs Discord Bot Template 🌟](https://github.com/mmvergara/nextjs-discord-bot-boilerplate) 48 | - [React Firebase🔥 Auth Template 🌟](https://github.com/mmvergara/react-firebase-auth-template) 49 | - [Golang Postgres Auth Template](https://github.com/mmvergara/golang-postgresql-auth-template) 50 | - [Vue Golang PostgresSql Auth Template](https://github.com/mmvergara/vue-golang-postgresql-auth-starter-template) 51 | - [Vue Supabase Auth Template](https://github.com/mmvergara/vue-supabase-auth-starter-template) 52 | - [Remix Drizzle Auth Template](https://github.com/mmvergara/remix-drizzle-auth-template) 53 | -------------------------------------------------------------------------------- /src/pages/auth/SignUpPage.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Link, Navigate } from "react-router-dom"; 3 | import { useSession } from "../../context/SessionContext"; 4 | import supabase from "../../supabase"; 5 | 6 | const SignUpPage = () => { 7 | // ============================== 8 | // If user is already logged in, redirect to home 9 | // This logic is being repeated in SignIn and SignUp.. 10 | const { session } = useSession(); 11 | if (session) return ; 12 | // maybe we can create a wrapper component for these pages 13 | // just like the ./router/AuthProtectedRoute.tsx? up to you. 14 | // ============================== 15 | const [status, setStatus] = useState(""); 16 | const [formValues, setFormValues] = useState({ 17 | email: "", 18 | password: "", 19 | }); 20 | 21 | const handleInputChange = (e: React.ChangeEvent) => { 22 | setFormValues({ ...formValues, [e.target.name]: e.target.value }); 23 | }; 24 | 25 | const handleSubmit = async (e: React.FormEvent) => { 26 | e.preventDefault(); 27 | setStatus("Creating account..."); 28 | const { error } = await supabase.auth.signUp({ 29 | email: formValues.email, 30 | password: formValues.password, 31 | }); 32 | if (error) { 33 | alert(error.message); 34 | } 35 | setStatus(""); 36 | }; 37 | 38 | return ( 39 |
40 | 41 | ◄ Home 42 | 43 |
44 |

Sign Up

45 |

52 | Demo app, please don't use your real email or password 53 |

54 | 60 | 66 | 67 | 68 | Already have an account? Sign In 69 | 70 | {status &&

{status}

} 71 |
72 |
73 | ); 74 | }; 75 | 76 | export default SignUpPage; 77 | -------------------------------------------------------------------------------- /src/pages/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import supabase from "../supabase"; 3 | import { useSession } from "../context/SessionContext"; 4 | 5 | const HomePage = () => { 6 | const { session } = useSession(); 7 | return ( 8 |
9 |
10 |

React Supabase Auth Template

11 |

Current User : {session?.user.email || "None"}

12 | {session ? ( 13 | 14 | ) : ( 15 | Sign In 16 | )} 17 | Protected Page 🛡️ 18 |
19 | 25 | 33 | 37 | 38 | Star us on Github 🌟 39 | 40 |
41 |
42 | ); 43 | }; 44 | 45 | export default HomePage; 46 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: "Inter", sans-serif !important; 6 | } 7 | body { 8 | background-color: #1c1c1c; /* Darker background, closer to Supabase style */ 9 | color: white; 10 | } 11 | main { 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | margin-top: 10vh; 16 | } 17 | a { 18 | text-decoration: none; 19 | } 20 | .header-text { 21 | display: flex; 22 | gap: 0.5em; 23 | align-items: center; 24 | padding: 0.5em 1em; 25 | font-weight: bold; 26 | background: none; 27 | text-align: center; 28 | } 29 | .main-container { 30 | display: flex; 31 | flex-direction: column; 32 | align-items: center; 33 | background-color: #2a2a2a; /* Slightly lighter than body, but still dark */ 34 | padding: 2em 2em; 35 | width: 100%; 36 | max-width: 500px; 37 | border-radius: 4px; /* Slightly rounded corners */ 38 | box-shadow: 0 0 60px rgba(0, 0, 0, 0.2); 39 | border-top: 5px; 40 | border-width: 10px 0px 0px 0px; 41 | border-style: solid; 42 | border-color: #3ecf8e; /* Supabase green */ 43 | } 44 | #github-repo-link { 45 | display: flex; 46 | gap: 0.5em; 47 | margin-top: 0; 48 | align-items: center; 49 | padding: 0.5em 1em; 50 | font-weight: bold; 51 | background: none; 52 | cursor: pointer; 53 | color: #3ecf8e; /* Supabase green */ 54 | } 55 | #github-repo-link:hover { 56 | background-color: rgba( 57 | 62, 58 | 207, 59 | 142, 60 | 0.1 61 | ); /* Supabase green with low opacity */ 62 | } 63 | input { 64 | padding: 1rem; 65 | border-radius: 4px; 66 | outline: none; 67 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); 68 | background-color: #3a3a3a; /* Slightly lighter than container background */ 69 | border: 1px solid #4a4a4a; 70 | font-size: 1rem; 71 | color: white; 72 | margin-top: 7px; 73 | width: 300px; 74 | } 75 | button, 76 | a { 77 | font-size: 16px; 78 | padding: 1em; 79 | background: none; 80 | cursor: pointer; 81 | color: white; 82 | background: #229f65; /* Supabase green */ 83 | border: none; 84 | border-radius: 4px; 85 | transition: background-color 0.3s; 86 | width: 300px; 87 | text-align: center; 88 | margin-top: 1em; 89 | display: flex; 90 | justify-content: center; 91 | align-items: center; 92 | height: 40px; 93 | font-weight: bold; 94 | border: 1px solid #33b379; /* Supabase green */ 95 | } 96 | 97 | a { 98 | border: none; 99 | } 100 | button:hover, 101 | a:hover { 102 | background: #2ebd80; /* Slightly darker Supabase green for hover */ 103 | } 104 | .home-link { 105 | margin-bottom: 1em; 106 | } 107 | .auth-link { 108 | background: transparent; 109 | color: #3ecf8e; /* Supabase green */ 110 | } 111 | .auth-link:hover { 112 | background: transparent; 113 | text-decoration: underline; 114 | } 115 | #divider { 116 | width: 70%; 117 | height: 2px; 118 | background-color: #3ecf8e; /* Supabase green */ 119 | margin: 1em 0; 120 | border-radius: 100%; 121 | } 122 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------