├── 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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------