├── vite.config.js ├── src ├── main.jsx ├── supabase-client.js ├── App.css ├── index.css ├── App.jsx └── assets │ └── react.svg ├── .gitignore ├── index.html ├── .eslintrc.cjs ├── package.json ├── public └── vite.svg └── README.md /vite.config.js: -------------------------------------------------------------------------------- 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/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/supabase-client.js: -------------------------------------------------------------------------------- 1 | import { createClient } from "@supabase/supabase-js"; 2 | 3 | const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; 4 | const supabaseKey = import.meta.env.VITE_SUPABASE_KEY; 5 | 6 | const supabase = createClient(supabaseUrl, supabaseKey); 7 | 8 | export default supabase; 9 | -------------------------------------------------------------------------------- /.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 | 15 | .env 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supabase-crud", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@supabase/supabase-js": "^2.46.2", 14 | "react": "^18.3.1", 15 | "react-dom": "^18.3.1" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.3.3", 19 | "@types/react-dom": "^18.3.0", 20 | "@vitejs/plugin-react": "^4.3.1", 21 | "eslint": "^8.57.0", 22 | "eslint-plugin-react": "^7.34.3", 23 | "eslint-plugin-react-hooks": "^4.6.2", 24 | "eslint-plugin-react-refresh": "^0.4.7", 25 | "vite": "^5.3.4" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import "./App.css"; 3 | import supabase from "./supabase-client"; 4 | 5 | function App() { 6 | const [todoList, setTodoList] = useState([]); 7 | const [newTodo, setNewTodo] = useState(""); 8 | 9 | useEffect(() => { 10 | fetchTodos(); 11 | }, []); 12 | 13 | const fetchTodos = async () => { 14 | const { data, error } = await supabase.from("TodoList").select("*"); 15 | if (error) { 16 | console.log("Error fetching: ", error); 17 | } else { 18 | setTodoList(data); 19 | } 20 | }; 21 | 22 | const addTodo = async () => { 23 | const newTodoData = { 24 | name: newTodo, 25 | isCompleted: false, 26 | }; 27 | const { data, error } = await supabase 28 | .from("TodoList") 29 | .insert([newTodoData]) 30 | .single(); 31 | 32 | if (error) { 33 | console.log("Error adding todo: ", error); 34 | } else { 35 | setTodoList((prev) => [...prev, data]); 36 | setNewTodo(""); 37 | } 38 | }; 39 | 40 | const completeTask = async (id, isCompleted) => { 41 | const { data, error } = await supabase 42 | .from("TodoList") 43 | .update({ isCompleted: !isCompleted }) 44 | .eq("id", id); 45 | 46 | if (error) { 47 | console.log("error toggling task: ", error); 48 | } else { 49 | const updatedTodoList = todoList.map((todo) => 50 | todo.id === id ? { ...todo, isCompleted: !isCompleted } : todo 51 | ); 52 | setTodoList(updatedTodoList); 53 | } 54 | }; 55 | 56 | const deleteTask = async (id) => { 57 | const { data, error } = await supabase 58 | .from("TodoList") 59 | .delete() 60 | .eq("id", id); 61 | 62 | if (error) { 63 | console.log("error deleting task: ", error); 64 | } else { 65 | setTodoList((prev) => prev.filter((todo) => todo.id !== id)); 66 | } 67 | }; 68 | 69 | return ( 70 |
71 | {" "} 72 |

Todo List

73 |
74 | setNewTodo(e.target.value)} 79 | /> 80 | 81 |
82 | 94 |
95 | ); 96 | } 97 | 98 | export default App; 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Todo List App with Supabase, React, and Vite 2 | 3 | This is a simple Todo List application built with **React**, **Supabase**, and **Vite**. It allows users to create, read, update, and delete their own tasks, stored securely in a Supabase database. 4 | 5 | Check out my YouTube channel for more tutorials: [@pedrotechnologies](https://www.youtube.com/@pedrotechnologies) 6 | 7 | ## Features 8 | 9 | - Create a new todo item 10 | - Read (view) all todos created by the authenticated user 11 | - Update the name or completion status of a todo 12 | - Delete a todo item 13 | 14 | ## Technologies Used 15 | 16 | - **React**: Frontend library for building the user interface 17 | - **Supabase**: Backend as a service for managing the database and authentication 18 | - **Vite**: Build tool for fast development 19 | - **PostgreSQL**: Database for storing todo items 20 | 21 | ## Setup 22 | 23 | Follow these steps to set up the project locally. 24 | 25 | ### 1. Clone the repository 26 | 27 | Clone this repository to your local machine: 28 | 29 | ``` 30 | git clone https://github.com/yourusername/todo-app.git 31 | cd todo-app 32 | ``` 33 | 34 | ### 2. Install dependencies 35 | 36 | Install the required dependencies using npm: 37 | 38 | ``` 39 | npm install 40 | ``` 41 | 42 | ### 3. Set up Supabase 43 | 44 | 1. Go to [Supabase](https://supabase.com) and create a new project. 45 | 2. Set up your database by creating a `todos` table with the following columns: 46 | - `id` (Integer, Primary Key, Auto-increment) 47 | - `created_at` (Timestamp, default to `now()`) 48 | - `name` (Text) 49 | - `isCompleted` (Boolean) 50 | 3. Copy your Supabase **URL** and **anon key** from the project settings. 51 | 52 | ### 4. Configure Supabase Client 53 | 54 | In your project folder, create a new file named `src/supabaseClient.js` and paste the following configuration: 55 | 56 | ```javascript 57 | import { createClient } from "@supabase/supabase-js"; 58 | 59 | // Initialize Supabase client with your credentials 60 | const supabaseUrl = "https://your-project-id.supabase.co"; 61 | const supabaseKey = "your-public-anon-key"; 62 | const supabase = createClient(supabaseUrl, supabaseKey); 63 | 64 | export default supabase; 65 | ``` 66 | 67 | Replace `your-project-id` and `your-public-anon-key` with your actual Supabase credentials. 68 | 69 | ### 5. Run the Application 70 | 71 | Start the development server with: 72 | 73 | ``` 74 | npm run dev 75 | ``` 76 | 77 | Visit `http://localhost:5173` in your browser to see the Todo List app in action. 78 | 79 | ## Usage 80 | 81 | 1. **Create Todos**: Add a new todo by typing in the input field and clicking the "Add Todo" button. 82 | 2. **Read Todos**: View your todos listed below the input field. 83 | 3. **Update Todos**: Edit the name of a todo by clicking the "Edit" button and updating it. 84 | 4. **Mark as Completed**: Toggle the completion status of a todo by clicking the "Complete" button. 85 | 5. **Delete Todos**: Remove a todo by clicking the "Delete" button. 86 | 87 | ## Contributing 88 | 89 | Feel free to fork this project and submit pull requests for bug fixes or enhancements. 90 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------