├── .DS_Store
├── Part-1
├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── appwrite
│ │ ├── config.js
│ │ └── databases.js
│ ├── assets
│ │ ├── DeleteIcon.jsx
│ │ └── react.svg
│ ├── components
│ │ ├── Note.jsx
│ │ └── NoteForm.jsx
│ ├── index.css
│ ├── main.jsx
│ └── pages
│ │ ├── LoginRegister.jsx
│ │ └── Notes.jsx
└── vite.config.js
├── Part-2-Template-Only
├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.jsx
│ ├── assets
│ │ ├── DeleteIcon.jsx
│ │ └── react.svg
│ ├── components
│ │ ├── Note.jsx
│ │ └── NoteForm.jsx
│ ├── data.json
│ ├── index.css
│ ├── main.jsx
│ └── pages
│ │ ├── LoginRegister.jsx
│ │ └── Notes.jsx
└── vite.config.js
├── Part-2
├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── appwrite
│ │ ├── config.js
│ │ └── databases.js
│ ├── assets
│ │ ├── DeleteIcon.jsx
│ │ └── react.svg
│ ├── components
│ │ ├── Note.jsx
│ │ ├── NoteForm.jsx
│ │ └── ThemeOption.jsx
│ ├── index.css
│ ├── main.jsx
│ └── pages
│ │ ├── LoginRegister.jsx
│ │ └── Notes.jsx
└── vite.config.js
├── Part-3
├── .env.example
├── .eslintrc.cjs
├── .gitignore
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.jsx
│ ├── appwrite
│ │ ├── config.js
│ │ └── databases.js
│ ├── assets
│ │ ├── DeleteIcon.jsx
│ │ ├── LockIcon.jsx
│ │ ├── login.png
│ │ └── react.svg
│ ├── components
│ │ ├── LoginForm.jsx
│ │ ├── LogoutButton.jsx
│ │ ├── Note.jsx
│ │ ├── NoteForm.jsx
│ │ ├── PrivateRoutes.jsx
│ │ ├── RegisterFrom.jsx
│ │ └── ThemeOption.jsx
│ ├── context
│ │ └── AuthContext.jsx
│ ├── index.css
│ ├── main.jsx
│ └── pages
│ │ ├── LoginRegister.jsx
│ │ └── Notes.jsx
└── vite.config.js
├── README.md
├── assets
├── .env.example
└── notes.png
└── package-lock.json
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Appwrite-React-quickstart/658881d728939d622e0f5ac9c4b8eb093a2cbcb2/.DS_Store
--------------------------------------------------------------------------------
/Part-1/.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-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/Part-1/.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 | .env
10 |
11 | node_modules
12 | dist
13 | dist-ssr
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/Part-1/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/Part-1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Part-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo",
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 | "appwrite": "^14.0.1",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-router-dom": "^6.23.1"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.2.15",
20 | "@types/react-dom": "^18.2.7",
21 | "@vitejs/plugin-react": "^4.0.3",
22 | "eslint": "^8.45.0",
23 | "eslint-plugin-react": "^7.32.2",
24 | "eslint-plugin-react-hooks": "^4.6.0",
25 | "eslint-plugin-react-refresh": "^0.4.3",
26 | "vite": "^4.4.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Part-1/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Part-1/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Appwrite-React-quickstart/658881d728939d622e0f5ac9c4b8eb093a2cbcb2/Part-1/src/App.css
--------------------------------------------------------------------------------
/Part-1/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Routes, Route } from "react-router-dom";
2 | import Notes from "./pages/Notes";
3 | import LoginRegister from "./pages/LoginRegister";
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 |
11 | } path="/" />
12 | } path="/login" />
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/Part-1/src/appwrite/config.js:
--------------------------------------------------------------------------------
1 | import { Client, Databases } from "appwrite";
2 |
3 | const client = new Client();
4 |
5 | client
6 | .setEndpoint(import.meta.env.VITE_ENDPOINT)
7 | .setProject(import.meta.env.VITE_PROJECT_ID);
8 |
9 | const databases = new Databases(client);
10 |
11 | export { client, databases };
12 |
--------------------------------------------------------------------------------
/Part-1/src/appwrite/databases.js:
--------------------------------------------------------------------------------
1 | import { databases } from "./config";
2 | import { ID } from "appwrite";
3 |
4 | const db = {};
5 |
6 | const collections = [
7 | {
8 | dbId: import.meta.env.VITE_DATABASE_ID,
9 | id: import.meta.env.VITE_COLLECTION_ID_NOTES,
10 | name: "notes",
11 | },
12 | ];
13 |
14 | collections.forEach((col) => {
15 | db[col.name] = {
16 | create: (payload, permissions, id = ID.unique()) =>
17 | databases.createDocument(
18 | col.dbId,
19 | col.id,
20 | id,
21 | payload,
22 | permissions
23 | ),
24 | update: (id, payload, permissions) =>
25 | databases.updateDocument(
26 | col.dbId,
27 | col.id,
28 | id,
29 | payload,
30 | permissions
31 | ),
32 | delete: (id) => databases.deleteDocument(col.dbId, col.id, id),
33 |
34 | list: (queries = []) =>
35 | databases.listDocuments(col.dbId, col.id, queries),
36 |
37 | get: (id) => databases.getDocument(col.dbId, col.id, id),
38 | };
39 | });
40 |
41 | export default db;
42 |
--------------------------------------------------------------------------------
/Part-1/src/assets/DeleteIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const DeleteIcon = ({ size = 24, stroke = "#f87171" }) => {
4 | return (
5 |
15 |
16 |
20 |
21 | );
22 | };
23 |
24 | export default DeleteIcon;
25 |
--------------------------------------------------------------------------------
/Part-1/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Part-1/src/components/Note.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import db from "../appwrite/databases";
3 | import DeleteIcon from "../assets/DeleteIcon";
4 |
5 | function Note({ setNotes, noteData }) {
6 | const [note, setNote] = useState(noteData);
7 |
8 | const handleUpdate = async () => {
9 | const completed = !note.completed;
10 | db.notes.update(note.$id, { completed });
11 | setNote({ ...note, completed: completed });
12 | };
13 |
14 | const handleDelete = async () => {
15 | db.notes.delete(note.$id);
16 | setNotes((prevState) => prevState.filter((i) => i.$id !== note.$id));
17 | };
18 |
19 | return (
20 |
21 |
22 | {note.completed ? {note.body} : <>{note.body}>}
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export default Note;
33 |
--------------------------------------------------------------------------------
/Part-1/src/components/NoteForm.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import db from "../appwrite/databases";
3 |
4 | function NoteForm({ setNotes }) {
5 | const handleAdd = async (e) => {
6 | e.preventDefault();
7 | const noteBody = e.target.body.value;
8 |
9 | if (noteBody === "") return;
10 |
11 | try {
12 | const payload = { body: noteBody };
13 |
14 | const response = await db.notes.create(payload);
15 | setNotes((prevState) => [response, ...prevState]);
16 |
17 | e.target.reset();
18 | } catch (error) {
19 | console.error(error);
20 | }
21 | };
22 |
23 | return (
24 |
31 | );
32 | }
33 |
34 | export default NoteForm;
35 |
--------------------------------------------------------------------------------
/Part-1/src/index.css:
--------------------------------------------------------------------------------
1 | :root{
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-size: 18px;
5 | font-weight: 400;
6 |
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | -webkit-text-size-adjust: 100%;
10 |
11 |
12 |
13 | --primary-background-color: #fff;
14 | --secondary-background-color:#f7f7f7;
15 |
16 | --primary-text-color:#000;
17 | --secondary-text-color:#4b4c53;
18 |
19 | --primary-border-color:#4b4c53;
20 |
21 | --button-solid-background-color:#000;
22 | --button-solid-text-color:#fff;
23 |
24 | --button-outlined-background-color:transparent;
25 | --button-outlined-text-color:#000;
26 |
27 | --alert-color:#FFF9E7;
28 | --alert-border-color:#FFBF00;
29 | --alert-text-color:#b8952f;
30 | }
31 |
32 | [data-theme="dark"]{
33 | --primary-background-color: #1f2028;
34 | --secondary-background-color:#2e3039;
35 |
36 | --primary-text-color:rgba(255, 255, 255, 0.87);
37 | --secondary-text-color:#4b4c53;
38 |
39 | --primary-border-color:#4b4c53;
40 |
41 | --button-solid-background-color:#fff;
42 | --button-solid-text-color:#000;
43 |
44 | --button-outlined-background-color:transparent;
45 | --button-outlined-text-color:#fff;
46 |
47 | }
48 |
49 | [data-theme="purple"]{
50 | --primary-background-color: rgba(24, 16, 40, 1);
51 | --secondary-background-color:rgba(44, 36, 70, 1);
52 |
53 | --primary-text-color:rgba(255, 255, 255, 0.87);
54 | --secondary-text-color:#8549a7;
55 |
56 | --primary-border-color:#8549a7;
57 |
58 | --button-solid-background-color:#fff;
59 | --button-solid-text-color:#000;
60 |
61 | --button-outlined-background-color:transparent;
62 | --button-outlined-text-color:#fff;
63 |
64 | }
65 |
66 |
67 | html, body, #root{
68 | padding: 0;
69 | margin: 0;
70 | height: 100%;
71 | width: 100%;
72 | }
73 |
74 | div{
75 | overflow-y: auto;
76 | }
77 |
78 | #app{
79 | height: 100%;
80 | background-color: var(--primary-background-color);
81 | color:var(--primary-text-color);
82 | transition: background-color 0.5s ease, color 0.5s ease;
83 |
84 | }
85 |
86 | #container{
87 | max-width: 600px;
88 | margin: 0em auto;
89 | padding: 2em 0;
90 | }
91 |
92 | #todo-form{
93 | margin: 2em 0;
94 | }
95 |
96 | #todo-form input{
97 | border: 1px solid var(--primary-border-color);
98 | padding: 1em;
99 | border-radius: 10px;
100 | width: 100%;
101 | font-size: 18px;
102 | box-sizing: border-box;
103 | background-color: transparent;
104 | color: var(--primary-text-color);
105 | }
106 |
107 | .note-wrapper{
108 | /* border-bottom:1px solid hsla(0, 1%,22%,1); */
109 | background-color: var(--secondary-background-color);
110 | padding: 1em;
111 | margin: 1em 0;
112 | border-radius: 5px;
113 | display: flex;
114 | justify-content: space-between;
115 | /* align-items: center; */
116 | }
117 |
118 | .note-body{
119 | cursor: pointer;
120 | }
121 |
122 | .delete{
123 | cursor: pointer;
124 | }
125 |
126 | .auth-field-wrapper{
127 |
128 | display: flex;
129 | flex-direction: column;
130 | margin: 1em 0 ;
131 |
132 |
133 | }
134 |
135 | .auth-field-wrapper input[type='text'], input[type='email'], input[type='password']{
136 | border: none;
137 | outline: none;
138 | background-color: transparent;
139 | height: 75px;
140 | box-sizing: border-box;
141 | border-bottom: 1px solid var(--primary-border-color);
142 | font-size: 18px;
143 | color: var(--button-outlined-text-color);
144 | }
145 |
146 | .auth-field-wrapper input[type='submit']{
147 | padding: 1em 1.5em;
148 |
149 | width: fit-content;
150 | font-size: 18px;
151 | font-weight: 500;
152 | border:0;
153 | border-radius: 50px;
154 | background-color: var(--button-solid-background-color);
155 | color: var(--button-solid-text-color);
156 | }
157 |
158 | button{
159 | padding: 1em 1.5em;
160 |
161 | width: fit-content;
162 | font-size: 18px;
163 | font-weight: 500;
164 | border:0;
165 | border-radius: 50px;
166 | border: 1.5px solid var(--button-outlined-text-color);
167 | background-color: transparent;
168 | color: var(--button-outlined-text-color);;
169 | }
170 |
171 | input[type="submit"], button{
172 | cursor: pointer;
173 | }
174 |
175 | #logout-btn{
176 | position: fixed;
177 | bottom: 2em;
178 | right: 2em;
179 | }
180 |
181 | .tag{
182 | background-color:#61b593 ;
183 | padding: 0.5em 1em;
184 | border-radius: 20px;
185 | width: fit-content;
186 | font-size: 12px;
187 | font-weight: 700;
188 | color: #fff;
189 | }
190 |
191 | #pro-header{
192 | display: flex;
193 | justify-content: space-between;
194 | align-items: center;
195 | }
196 |
197 | #subscribe-prompt{
198 | display: flex;
199 | align-items:center;
200 | gap: 0.5em;
201 | background-color: var(--alert-color);
202 | border: 1px solid var(--alert-border-color);
203 | padding: 0 1em;
204 | border-radius: 5px;
205 | color: var(--alert-text-color);
206 | cursor: pointer;
207 | transition: 0.3s;
208 | }
209 |
210 | #subscribe-prompt:hover{
211 | color: #886f23;
212 | background-color: #f0e4c1;
213 | }
214 |
215 | .theme-options{
216 | display: flex;
217 | gap:10px;
218 | }
219 |
220 | .theme-option{
221 | height: 25px;
222 | width: 25px;
223 | border: 1px solid;
224 | border-radius: 50%;
225 | cursor: pointer;
226 | }
227 |
228 |
229 |
230 | @media screen and (max-width:600px){
231 | #app{
232 | padding: 0 1em;
233 | }
234 | }
--------------------------------------------------------------------------------
/Part-1/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 |
--------------------------------------------------------------------------------
/Part-1/src/pages/LoginRegister.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function LoginRegister() {
4 | return LoginRegister
;
5 | }
6 |
7 | export default LoginRegister;
8 |
--------------------------------------------------------------------------------
/Part-1/src/pages/Notes.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import db from "../appwrite/databases";
3 | import NoteForm from "../components/NoteForm";
4 | import { Query } from "appwrite";
5 | import Note from "../components/Note";
6 |
7 | function Notes() {
8 | const [notes, setNotes] = useState([]);
9 |
10 | useEffect(() => {
11 | init();
12 | }, []);
13 |
14 | const init = async () => {
15 | const response = await db.notes.list([Query.orderDesc("$createdAt")]);
16 | setNotes(response.documents);
17 | };
18 |
19 | return (
20 | <>
21 |
22 |
✍️ My Todo List
23 |
24 |
25 |
26 |
27 |
28 | {notes.map((note) => (
29 |
30 | ))}
31 |
32 | >
33 | );
34 | }
35 |
36 | export default Notes;
37 |
--------------------------------------------------------------------------------
/Part-1/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 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/.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-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/.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 | # 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 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo",
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 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-router-dom": "^6.23.1"
16 | },
17 | "devDependencies": {
18 | "@types/react": "^18.2.15",
19 | "@types/react-dom": "^18.2.7",
20 | "@vitejs/plugin-react": "^4.0.3",
21 | "eslint": "^8.45.0",
22 | "eslint-plugin-react": "^7.32.2",
23 | "eslint-plugin-react-hooks": "^4.6.0",
24 | "eslint-plugin-react-refresh": "^0.4.3",
25 | "vite": "^4.4.5"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Routes, Route } from "react-router-dom";
2 | import Notes from "./pages/Notes";
3 | import LoginRegister from "./pages/LoginRegister";
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 |
11 | } path="/" />
12 | } path="/login" />
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default App;
21 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/assets/DeleteIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const DeleteIcon = ({ size = 24, stroke = "#f87171" }) => {
4 | return (
5 |
15 |
16 |
20 |
21 | );
22 | };
23 |
24 | export default DeleteIcon;
25 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/components/Note.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import DeleteIcon from "../assets/DeleteIcon";
3 |
4 | function Note({ noteData }) {
5 | const [note, setNote] = useState(noteData);
6 |
7 | return (
8 |
9 |
10 | {note.completed ? {note.body} : <>{note.body}>}
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | export default Note;
21 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/components/NoteForm.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function NoteForm() {
4 | return (
5 |
13 | );
14 | }
15 |
16 | export default NoteForm;
17 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "$id":1,
4 | "body":"Wash car",
5 | "completed":false
6 | },
7 | {
8 | "$id":2,
9 | "body":"Engineering sync with team",
10 | "completed":false
11 | },
12 | {
13 | "$id":3,
14 | "body":"Clean house",
15 | "completed":true
16 | }
17 | ]
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/index.css:
--------------------------------------------------------------------------------
1 | :root{
2 |
3 | --primary-background-color: #fff;
4 | --secondary-background-color:#f7f7f7;
5 |
6 | --primary-text-color:#000;
7 | --secondary-text-color:#4b4c53;
8 |
9 | --primary-border-color:#4b4c53;
10 |
11 | --button-solid-background-color:#000;
12 | --button-solid-text-color:#fff;
13 |
14 | --button-outlined-background-color:transparent;
15 | --button-outlined-text-color:#000;
16 | }
17 |
18 | html, body, #root{
19 | padding: 0;
20 | margin: 0;
21 | height: 100%;
22 | width: 100%;
23 |
24 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
25 | line-height: 1.5;
26 | font-size: 18px;
27 | font-weight: 400;
28 |
29 | -webkit-font-smoothing: antialiased;
30 | -moz-osx-font-smoothing: grayscale;
31 | -webkit-text-size-adjust: 100%;
32 | }
33 |
34 | div{
35 | overflow-y: auto;
36 | }
37 |
38 | #app{
39 | height: 100%;
40 | background-color: var(--primary-background-color);
41 | color:var(--primary-text-color);
42 |
43 | }
44 |
45 | #container{
46 | max-width: 600px;
47 | margin: 0em auto;
48 | padding: 2em 0;
49 | }
50 |
51 | #todo-form{
52 | margin: 2em 0;
53 | }
54 |
55 | #todo-form input{
56 | border: 1px solid var(--primary-border-color);
57 | padding: 1em;
58 | border-radius: 10px;
59 | width: 100%;
60 | font-size: 18px;
61 | box-sizing: border-box;
62 | background-color: transparent;
63 | color: var(--primary-text-color);
64 | }
65 |
66 | .note-wrapper{
67 | /* border-bottom:1px solid hsla(0, 1%,22%,1); */
68 | background-color: var(--secondary-background-color);
69 | padding: 1em;
70 | margin: 1em 0;
71 | border-radius: 5px;
72 | display: flex;
73 | justify-content: space-between;
74 | /* align-items: center; */
75 | }
76 |
77 | .note-body{
78 | cursor: pointer;
79 | }
80 |
81 | .delete{
82 | cursor: pointer;
83 | }
84 |
85 | .auth-field-wrapper{
86 |
87 | display: flex;
88 | flex-direction: column;
89 | margin: 1em 0 ;
90 |
91 |
92 | }
93 |
94 | .auth-field-wrapper input[type='text'], input[type='email'], input[type='password']{
95 | border: none;
96 | outline: none;
97 | background-color: transparent;
98 | height: 75px;
99 | box-sizing: border-box;
100 | border-bottom: 1px solid var(--primary-border-color);
101 | font-size: 18px;
102 | color: var(--button-outlined-text-color);
103 | }
104 |
105 | .auth-field-wrapper input[type='submit']{
106 | padding: 1em 1.5em;
107 |
108 | width: fit-content;
109 | font-size: 18px;
110 | font-weight: 500;
111 | border:0;
112 | border-radius: 50px;
113 | background-color: var(--button-solid-background-color);
114 | color: var(--button-solid-text-color);
115 | }
116 |
117 | button{
118 | padding: 1em 1.5em;
119 |
120 | width: fit-content;
121 | font-size: 18px;
122 | font-weight: 500;
123 | border:0;
124 | border-radius: 50px;
125 | border: 1.5px solid var(--button-outlined-text-color);
126 | background-color: transparent;
127 | color: var(--button-outlined-text-color);;
128 | }
129 |
130 | input[type="submit"], button{
131 | cursor: pointer;
132 | }
133 |
134 | #logout-btn{
135 | position: fixed;
136 | bottom: 2em;
137 | right: 2em;
138 | }
139 |
140 | .tag{
141 | background-color:#61b593 ;
142 | padding: 0.5em 1em;
143 | border-radius: 20px;
144 | width: fit-content;
145 | font-size: 12px;
146 | font-weight: 700;
147 | color: #fff;
148 | }
149 |
150 | #pro-header{
151 | display: flex;
152 | justify-content: space-between;
153 | align-items: center;
154 | }
155 |
156 | #subscribe-prompt{
157 | display: flex;
158 | align-items:center;
159 | gap: 0.5em;
160 | background-color: var(--alert-color);
161 | border: 1px solid var(--alert-border-color);
162 | padding: 0 1em;
163 | border-radius: 5px;
164 | color: var(--alert-text-color);
165 | cursor: pointer;
166 | transition: 0.3s;
167 | }
168 |
169 | #subscribe-prompt:hover{
170 | color: #886f23;
171 | background-color: #f0e4c1;
172 | }
173 |
174 | @media screen and (max-width:600px){
175 | #app{
176 | padding: 0 1em;
177 | }
178 | }
--------------------------------------------------------------------------------
/Part-2-Template-Only/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 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/pages/LoginRegister.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function LoginRegister() {
4 | return LoginRegister
;
5 | }
6 |
7 | export default LoginRegister;
8 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/src/pages/Notes.jsx:
--------------------------------------------------------------------------------
1 | import NoteForm from "../components/NoteForm";
2 | import Note from "../components/Note";
3 | import notes from "../data.json";
4 |
5 | function Notes() {
6 | return (
7 | <>
8 |
9 |
✍️ My Todo List
10 |
11 |
12 |
13 |
14 |
15 | {notes.map((note) => (
16 |
17 | ))}
18 |
19 | >
20 | );
21 | }
22 |
23 | export default Notes;
24 |
--------------------------------------------------------------------------------
/Part-2-Template-Only/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 |
--------------------------------------------------------------------------------
/Part-2/.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-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/Part-2/.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 | .env
10 |
11 | node_modules
12 | dist
13 | dist-ssr
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/Part-2/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/Part-2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Part-2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo",
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 | "appwrite": "^14.0.1",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-router-dom": "^6.23.1"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.2.15",
20 | "@types/react-dom": "^18.2.7",
21 | "@vitejs/plugin-react": "^4.0.3",
22 | "eslint": "^8.45.0",
23 | "eslint-plugin-react": "^7.32.2",
24 | "eslint-plugin-react-hooks": "^4.6.0",
25 | "eslint-plugin-react-refresh": "^0.4.3",
26 | "vite": "^4.4.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Part-2/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Part-2/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Appwrite-React-quickstart/658881d728939d622e0f5ac9c4b8eb093a2cbcb2/Part-2/src/App.css
--------------------------------------------------------------------------------
/Part-2/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Routes, Route } from "react-router-dom";
2 | import Notes from "./pages/Notes";
3 | import LoginRegister from "./pages/LoginRegister";
4 |
5 | function App() {
6 | const selectedTheme = localStorage.getItem("theme");
7 |
8 | if (selectedTheme) {
9 | document
10 | .querySelector("body")
11 | .setAttribute("data-theme", selectedTheme);
12 | }
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | } path="/" />
20 | } path="/login" />
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | export default App;
29 |
--------------------------------------------------------------------------------
/Part-2/src/appwrite/config.js:
--------------------------------------------------------------------------------
1 | import { Client, Databases } from "appwrite";
2 |
3 | const client = new Client();
4 |
5 | client
6 | .setEndpoint(import.meta.env.VITE_ENDPOINT)
7 | .setProject(import.meta.env.VITE_PROJECT_ID);
8 |
9 | const databases = new Databases(client);
10 |
11 | export { client, databases };
12 |
--------------------------------------------------------------------------------
/Part-2/src/appwrite/databases.js:
--------------------------------------------------------------------------------
1 | import { databases } from "./config";
2 | import { ID } from "appwrite";
3 |
4 | const db = {};
5 |
6 | const collections = [
7 | {
8 | dbId: import.meta.env.VITE_DATABASE_ID,
9 | id: import.meta.env.VITE_COLLECTION_ID_NOTES,
10 | name: "notes",
11 | },
12 | ];
13 |
14 | collections.forEach((col) => {
15 | db[col.name] = {
16 | create: (payload, permissions, id = ID.unique()) =>
17 | databases.createDocument(
18 | col.dbId,
19 | col.id,
20 | id,
21 | payload,
22 | permissions
23 | ),
24 | update: (id, payload, permissions) =>
25 | databases.updateDocument(
26 | col.dbId,
27 | col.id,
28 | id,
29 | payload,
30 | permissions
31 | ),
32 | delete: (id) => databases.deleteDocument(col.dbId, col.id, id),
33 |
34 | list: (queries = []) =>
35 | databases.listDocuments(col.dbId, col.id, queries),
36 |
37 | get: (id) => databases.getDocument(col.dbId, col.id, id),
38 | };
39 | });
40 |
41 | export default db;
42 |
--------------------------------------------------------------------------------
/Part-2/src/assets/DeleteIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const DeleteIcon = ({ size = 24, stroke = "#f87171" }) => {
4 | return (
5 |
15 |
16 |
20 |
21 | );
22 | };
23 |
24 | export default DeleteIcon;
25 |
--------------------------------------------------------------------------------
/Part-2/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Part-2/src/components/Note.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import db from "../appwrite/databases";
3 | import DeleteIcon from "../assets/DeleteIcon";
4 |
5 | function Note({ setNotes, noteData }) {
6 | const [note, setNote] = useState(noteData);
7 |
8 | const handleUpdate = async () => {
9 | const completed = !note.completed;
10 | db.notes.update(note.$id, { completed });
11 | setNote({ ...note, completed: completed });
12 | };
13 |
14 | const handleDelete = async () => {
15 | db.notes.delete(note.$id);
16 | setNotes((prevState) => prevState.filter((i) => i.$id !== note.$id));
17 | };
18 |
19 | return (
20 |
21 |
22 | {note.completed ? {note.body} : <>{note.body}>}
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export default Note;
33 |
--------------------------------------------------------------------------------
/Part-2/src/components/NoteForm.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import db from "../appwrite/databases";
3 |
4 | function NoteForm({ setNotes }) {
5 | const handleAdd = async (e) => {
6 | e.preventDefault();
7 | const noteBody = e.target.body.value;
8 |
9 | if (noteBody === "") return;
10 |
11 | try {
12 | const payload = { body: noteBody };
13 |
14 | const response = await db.notes.create(payload);
15 | setNotes((prevState) => [response, ...prevState]);
16 |
17 | e.target.reset();
18 | } catch (error) {
19 | console.error(error);
20 | }
21 | };
22 |
23 | return (
24 |
31 | );
32 | }
33 |
34 | export default NoteForm;
35 |
--------------------------------------------------------------------------------
/Part-2/src/components/ThemeOption.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ThemeOption = ({ theme }) => {
4 | const setTheme = () => {
5 | document.querySelector("body").setAttribute("data-theme", theme);
6 | localStorage.setItem("theme", theme);
7 | };
8 |
9 | return (
10 |
15 | );
16 | };
17 |
18 | export default ThemeOption;
19 |
--------------------------------------------------------------------------------
/Part-2/src/index.css:
--------------------------------------------------------------------------------
1 | :root{
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-size: 18px;
5 | font-weight: 400;
6 |
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | -webkit-text-size-adjust: 100%;
10 |
11 |
12 |
13 | --primary-background-color: #fff;
14 | --secondary-background-color:#f7f7f7;
15 |
16 | --primary-text-color:#000;
17 | --secondary-text-color:#4b4c53;
18 |
19 | --primary-border-color:#4b4c53;
20 |
21 | --button-solid-background-color:#000;
22 | --button-solid-text-color:#fff;
23 |
24 | --button-outlined-background-color:transparent;
25 | --button-outlined-text-color:#000;
26 |
27 | --alert-color:#FFF9E7;
28 | --alert-border-color:#FFBF00;
29 | --alert-text-color:#b8952f;
30 | }
31 |
32 | [data-theme="dark"]{
33 | --primary-background-color: #1f2028;
34 | --secondary-background-color:#2e3039;
35 |
36 | --primary-text-color:rgba(255, 255, 255, 0.87);
37 | --secondary-text-color:#4b4c53;
38 |
39 | --primary-border-color:#4b4c53;
40 |
41 | --button-solid-background-color:#fff;
42 | --button-solid-text-color:#000;
43 |
44 | --button-outlined-background-color:transparent;
45 | --button-outlined-text-color:#fff;
46 |
47 | }
48 |
49 | [data-theme="purple"]{
50 | --primary-background-color: rgba(24, 16, 40, 1);
51 | --secondary-background-color:rgba(44, 36, 70, 1);
52 |
53 | --primary-text-color:rgba(255, 255, 255, 0.87);
54 | --secondary-text-color:#8549a7;
55 |
56 | --primary-border-color:#8549a7;
57 |
58 | --button-solid-background-color:#fff;
59 | --button-solid-text-color:#000;
60 |
61 | --button-outlined-background-color:transparent;
62 | --button-outlined-text-color:#fff;
63 |
64 | }
65 |
66 |
67 | html, body, #root{
68 | padding: 0;
69 | margin: 0;
70 | height: 100%;
71 | width: 100%;
72 | }
73 |
74 | div{
75 | overflow-y: auto;
76 | }
77 |
78 | #app{
79 | height: 100%;
80 | background-color: var(--primary-background-color);
81 | color:var(--primary-text-color);
82 | transition: background-color 0.5s ease, color 0.5s ease;
83 |
84 | }
85 |
86 | #container{
87 | max-width: 600px;
88 | margin: 0em auto;
89 | padding: 2em 0;
90 | }
91 |
92 | #todo-form{
93 | margin: 2em 0;
94 | }
95 |
96 | #todo-form input{
97 | border: 1px solid var(--primary-border-color);
98 | padding: 1em;
99 | border-radius: 10px;
100 | width: 100%;
101 | font-size: 18px;
102 | box-sizing: border-box;
103 | background-color: transparent;
104 | color: var(--primary-text-color);
105 | }
106 |
107 | .note-wrapper{
108 | /* border-bottom:1px solid hsla(0, 1%,22%,1); */
109 | background-color: var(--secondary-background-color);
110 | padding: 1em;
111 | margin: 1em 0;
112 | border-radius: 5px;
113 | display: flex;
114 | justify-content: space-between;
115 | /* align-items: center; */
116 | }
117 |
118 | .note-body{
119 | cursor: pointer;
120 | }
121 |
122 | .delete{
123 | cursor: pointer;
124 | }
125 |
126 | .auth-field-wrapper{
127 |
128 | display: flex;
129 | flex-direction: column;
130 | margin: 1em 0 ;
131 |
132 |
133 | }
134 |
135 | .auth-field-wrapper input[type='text'], input[type='email'], input[type='password']{
136 | border: none;
137 | outline: none;
138 | background-color: transparent;
139 | height: 75px;
140 | box-sizing: border-box;
141 | border-bottom: 1px solid var(--primary-border-color);
142 | font-size: 18px;
143 | color: var(--button-outlined-text-color);
144 | }
145 |
146 | .auth-field-wrapper input[type='submit']{
147 | padding: 1em 1.5em;
148 |
149 | width: fit-content;
150 | font-size: 18px;
151 | font-weight: 500;
152 | border:0;
153 | border-radius: 50px;
154 | background-color: var(--button-solid-background-color);
155 | color: var(--button-solid-text-color);
156 | }
157 |
158 | button{
159 | padding: 1em 1.5em;
160 |
161 | width: fit-content;
162 | font-size: 18px;
163 | font-weight: 500;
164 | border:0;
165 | border-radius: 50px;
166 | border: 1.5px solid var(--button-outlined-text-color);
167 | background-color: transparent;
168 | color: var(--button-outlined-text-color);;
169 | }
170 |
171 | input[type="submit"], button{
172 | cursor: pointer;
173 | }
174 |
175 | #logout-btn{
176 | position: fixed;
177 | bottom: 2em;
178 | right: 2em;
179 | }
180 |
181 | .tag{
182 | background-color:#61b593 ;
183 | padding: 0.5em 1em;
184 | border-radius: 20px;
185 | width: fit-content;
186 | font-size: 12px;
187 | font-weight: 700;
188 | color: #fff;
189 | }
190 |
191 | #pro-header{
192 | display: flex;
193 | justify-content: space-between;
194 | align-items: center;
195 | }
196 |
197 | #subscribe-prompt{
198 | display: flex;
199 | align-items:center;
200 | gap: 0.5em;
201 | background-color: var(--alert-color);
202 | border: 1px solid var(--alert-border-color);
203 | padding: 0 1em;
204 | border-radius: 5px;
205 | color: var(--alert-text-color);
206 | cursor: pointer;
207 | transition: 0.3s;
208 | }
209 |
210 | #subscribe-prompt:hover{
211 | color: #886f23;
212 | background-color: #f0e4c1;
213 | }
214 |
215 | .theme-options{
216 | display: flex;
217 | gap:10px;
218 | }
219 |
220 | .theme-option{
221 | height: 25px;
222 | width: 25px;
223 | border: 1px solid;
224 | border-radius: 50%;
225 | cursor: pointer;
226 | }
227 |
228 | #theme-dark{
229 | background-color: #000;
230 | border-color: #fff;
231 | }
232 |
233 | #theme-light{
234 | background-color: #fff;
235 | border-color: #000;
236 | }
237 |
238 | #theme-purple{
239 | background-color: #954ebe;
240 | border-color: #954ebe;
241 | }
242 |
243 |
244 |
245 |
246 | @media screen and (max-width:600px){
247 | #app{
248 | padding: 0 1em;
249 | }
250 | }
--------------------------------------------------------------------------------
/Part-2/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 |
--------------------------------------------------------------------------------
/Part-2/src/pages/LoginRegister.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function LoginRegister() {
4 | return LoginRegister
;
5 | }
6 |
7 | export default LoginRegister;
8 |
--------------------------------------------------------------------------------
/Part-2/src/pages/Notes.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import db from "../appwrite/databases";
3 | import NoteForm from "../components/NoteForm";
4 | import { Query } from "appwrite";
5 | import Note from "../components/Note";
6 | import ThemeOption from "../components/ThemeOption";
7 |
8 | function Notes() {
9 | const [notes, setNotes] = useState([]);
10 |
11 | useEffect(() => {
12 | init();
13 | }, []);
14 |
15 | const init = async () => {
16 | const response = await db.notes.list([Query.orderDesc("$createdAt")]);
17 | setNotes(response.documents);
18 | };
19 |
20 | return (
21 | <>
22 |
23 |
✍️ My Todo List
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {notes.map((note) => (
36 |
37 | ))}
38 |
39 | >
40 | );
41 | }
42 |
43 | export default Notes;
44 |
--------------------------------------------------------------------------------
/Part-2/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 |
--------------------------------------------------------------------------------
/Part-3/.env.example:
--------------------------------------------------------------------------------
1 | VITE_ENDPOINT=https://cloud.appwrite.io/v1
2 | VITE_PROJECT_ID=
3 | VITE_DATABASE_ID=
4 | VITE_COLLECTION_ID_NOTES=
--------------------------------------------------------------------------------
/Part-3/.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-refresh/only-export-components': [
16 | 'warn',
17 | { allowConstantExport: true },
18 | ],
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/Part-3/.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 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/Part-3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Part-3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "appwrite-react-quickstart",
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 | "appwrite": "^14.0.1",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-router-dom": "^6.23.0"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.2.15",
20 | "@types/react-dom": "^18.2.7",
21 | "@vitejs/plugin-react": "^4.0.3",
22 | "eslint": "^8.45.0",
23 | "eslint-plugin-react": "^7.32.2",
24 | "eslint-plugin-react-hooks": "^4.6.0",
25 | "eslint-plugin-react-refresh": "^0.4.3",
26 | "vite": "^4.4.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Part-3/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Part-3/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { BrowserRouter, Routes, Route } from "react-router-dom";
3 | import { account } from "./appwrite/config";
4 | import Notes from "./pages/Notes";
5 | import LoginRegister from "./pages/LoginRegister";
6 | import { PrivateRoutes } from "./components/PrivateRoutes";
7 | import AuthProvider from "./context/AuthContext";
8 |
9 | function App() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | } path="/login" />
17 | }>
18 | } path="/" />
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | export default App;
29 |
--------------------------------------------------------------------------------
/Part-3/src/appwrite/config.js:
--------------------------------------------------------------------------------
1 | import { Client, Databases, Account } from "appwrite";
2 |
3 | const client = new Client();
4 |
5 | client
6 | .setEndpoint(import.meta.env.VITE_ENDPOINT)
7 | .setProject(import.meta.env.VITE_PROJECT_ID);
8 |
9 | const databases = new Databases(client);
10 | const account = new Account(client);
11 |
12 | export { client, databases, account };
13 |
--------------------------------------------------------------------------------
/Part-3/src/appwrite/databases.js:
--------------------------------------------------------------------------------
1 | import { databases } from "./config";
2 | import { ID } from "appwrite";
3 |
4 | const db = {};
5 |
6 | const collections = [
7 | {
8 | dbId: import.meta.env.VITE_DATABASE_ID,
9 | id: import.meta.env.VITE_COLLECTION_ID_NOTES,
10 | name: "notes",
11 | },
12 | ];
13 |
14 | collections.forEach((col) => {
15 | db[col.name] = {
16 | create: (payload, permissions, id = ID.unique()) =>
17 | databases.createDocument(
18 | col.dbId,
19 | col.id,
20 | id,
21 | payload,
22 | permissions
23 | ),
24 | update: (id, payload, permissions) =>
25 | databases.updateDocument(
26 | col.dbId,
27 | col.id,
28 | id,
29 | payload,
30 | permissions
31 | ),
32 | delete: (id) => databases.deleteDocument(col.dbId, col.id, id),
33 | list: (queries = []) =>
34 | databases.listDocuments(col.dbId, col.id, queries),
35 | get: (id) => databases.getDocument(col.dbId, col.id, id),
36 | };
37 | });
38 |
39 | export default db;
40 |
--------------------------------------------------------------------------------
/Part-3/src/assets/DeleteIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const DeleteIcon = ({ size = 24, stroke = "#f87171" }) => {
4 | return (
5 |
15 |
16 |
20 |
21 | );
22 | };
23 |
24 | export default DeleteIcon;
25 |
--------------------------------------------------------------------------------
/Part-3/src/assets/LockIcon.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const LockIcon = () => {
4 | return (
5 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default LockIcon;
23 |
--------------------------------------------------------------------------------
/Part-3/src/assets/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Appwrite-React-quickstart/658881d728939d622e0f5ac9c4b8eb093a2cbcb2/Part-3/src/assets/login.png
--------------------------------------------------------------------------------
/Part-3/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Part-3/src/components/LoginForm.jsx:
--------------------------------------------------------------------------------
1 | import { useRef } from "react";
2 | import { useAuth } from "../context/AuthContext";
3 |
4 | const LoginForm = ({ setActiveForm }) => {
5 | const { loginUser } = useAuth();
6 | const formRef = useRef(null);
7 |
8 | const handleLogin = (e) => {
9 | e.preventDefault();
10 | loginUser(formRef.current.email.value, formRef.current.password.value);
11 | };
12 |
13 | return (
14 |
15 |
Login
16 |
17 |
32 |
33 |
34 | Don't have an account?{" "}
35 | {
38 | setActiveForm("register");
39 | }}
40 | >
41 | Signup
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default LoginForm;
49 |
--------------------------------------------------------------------------------
/Part-3/src/components/LogoutButton.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useAuth } from "../context/AuthContext";
3 |
4 | const LogoutButton = () => {
5 | const { logoutUser } = useAuth();
6 |
7 | return (
8 |
9 | Logout
10 |
11 | );
12 | };
13 |
14 | export default LogoutButton;
15 |
--------------------------------------------------------------------------------
/Part-3/src/components/Note.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import DeleteIcon from "../assets/DeleteIcon";
3 | import db from "../appwrite/databases";
4 |
5 | const Note = ({ noteData, setNotes }) => {
6 | const [note, setNote] = useState(noteData);
7 |
8 | useEffect(() => {}, [note]);
9 |
10 | const handleDelete = async () => {
11 | db.notes.delete(note.$id);
12 | setNotes((prevState) => prevState.filter((i) => i.$id !== note.$id));
13 | };
14 |
15 | const handleUpdate = async () => {
16 | const completed = !note.completed;
17 | db.notes.update(note.$id, { completed });
18 | setNote({ ...note, completed: completed });
19 | };
20 | return (
21 |
22 |
23 | {note.completed ? {note.body} : <>{note.body}>}
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
33 | export default Note;
34 |
--------------------------------------------------------------------------------
/Part-3/src/components/NoteForm.jsx:
--------------------------------------------------------------------------------
1 | import db from "../appwrite/databases";
2 |
3 | const NoteForm = ({ setNotes }) => {
4 | const handleAdd = async (e) => {
5 | e.preventDefault();
6 | const noteBody = e.target.body.value;
7 | if (noteBody === "") return;
8 |
9 | try {
10 | const payload = {
11 | body: noteBody,
12 | };
13 |
14 | const response = await db.notes.create(payload);
15 | setNotes((prevState) => [response, ...prevState]);
16 |
17 | e.target.reset();
18 | } catch (error) {
19 | console.error(error);
20 | }
21 | };
22 |
23 | return (
24 |
31 | );
32 | };
33 |
34 | export default NoteForm;
35 |
--------------------------------------------------------------------------------
/Part-3/src/components/PrivateRoutes.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { Navigate, Outlet } from "react-router-dom";
3 | import { useAuth } from "../context/AuthContext";
4 |
5 | export const PrivateRoutes = (props) => {
6 | const { user } = useAuth();
7 | return user ? : ;
8 | };
9 |
--------------------------------------------------------------------------------
/Part-3/src/components/RegisterFrom.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const RegisterFrom = ({ setActiveForm }) => {
4 | return (
5 |
6 |
Register
7 |
27 |
28 |
29 | Already have an account?{" "}
30 | {
33 | setActiveForm("login");
34 | }}
35 | >
36 | Login
37 |
38 |
39 |
40 | );
41 | };
42 |
43 | export default RegisterFrom;
44 |
--------------------------------------------------------------------------------
/Part-3/src/components/ThemeOption.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ThemeOption = ({ bg, border, theme }) => {
4 | const changeTheme = (theme) => {
5 | console.log("Change theme");
6 | document.getElementById("app").setAttribute("data-theme", theme);
7 | };
8 |
9 | return (
10 | changeTheme(theme)}
12 | className="theme-option"
13 | style={{ backgroundColor: bg, borderColor: border }}
14 | >
15 | );
16 | };
17 |
18 | export default ThemeOption;
19 |
--------------------------------------------------------------------------------
/Part-3/src/context/AuthContext.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, createContext, useContext } from "react";
2 | import { account } from "../appwrite/config";
3 |
4 | const AuthContext = createContext();
5 |
6 | const AuthProvider = ({ children }) => {
7 | const [loading, setLoading] = useState(true);
8 | const [user, setUser] = useState(null);
9 |
10 | useEffect(() => {
11 | init();
12 | }, []);
13 |
14 | const init = async () => {
15 | const response = await checkUserStatus();
16 | setUser(response);
17 | setLoading(false);
18 | };
19 |
20 | const checkUserStatus = async () => {
21 | try {
22 | const userData = await account.get();
23 | return userData;
24 | } catch (error) {
25 | console.error(error);
26 | return null;
27 | }
28 | };
29 |
30 | const loginUser = async (email, password) => {
31 | setLoading(true);
32 | try {
33 | await account.createEmailPasswordSession(email, password);
34 | const response = await checkUserStatus();
35 | setUser(response);
36 | } catch (error) {
37 | console.error(error);
38 | }
39 | setLoading(false);
40 | };
41 |
42 | const logoutUser = async () => {
43 | await account.deleteSession("current");
44 | setUser(null);
45 | };
46 |
47 | const contextData = { user, loginUser, logoutUser };
48 |
49 | return (
50 |
51 | {loading ? Loading...
: children}
52 |
53 | );
54 | };
55 |
56 | const useAuth = () => {
57 | return useContext(AuthContext);
58 | };
59 |
60 | export { useAuth };
61 |
62 | export default AuthProvider;
63 |
--------------------------------------------------------------------------------
/Part-3/src/index.css:
--------------------------------------------------------------------------------
1 | :root{
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-size: 18px;
5 | font-weight: 400;
6 |
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | -webkit-text-size-adjust: 100%;
10 |
11 |
12 |
13 | --primary-background-color: #fff;
14 | --secondary-background-color:#f7f7f7;
15 |
16 | --primary-text-color:#000;
17 | --secondary-text-color:#4b4c53;
18 |
19 | --primary-border-color:#4b4c53;
20 |
21 | --button-solid-background-color:#000;
22 | --button-solid-text-color:#fff;
23 |
24 | --button-outlined-background-color:transparent;
25 | --button-outlined-text-color:#000;
26 |
27 | --alert-color:#FFF9E7;
28 | --alert-border-color:#FFBF00;
29 | --alert-text-color:#b8952f;
30 | }
31 |
32 | [data-theme="dark"]{
33 | --primary-background-color: #1f2028;
34 | --secondary-background-color:#2e3039;
35 |
36 | --primary-text-color:rgba(255, 255, 255, 0.87);
37 | --secondary-text-color:#4b4c53;
38 |
39 | --primary-border-color:#4b4c53;
40 |
41 | --button-solid-background-color:#fff;
42 | --button-solid-text-color:#000;
43 |
44 | --button-outlined-background-color:transparent;
45 | --button-outlined-text-color:#fff;
46 |
47 | }
48 |
49 | [data-theme="purple"]{
50 | --primary-background-color: rgba(24, 16, 40, 1);
51 | --secondary-background-color:rgba(44, 36, 70, 1);
52 |
53 | --primary-text-color:rgba(255, 255, 255, 0.87);
54 | --secondary-text-color:#8549a7;
55 |
56 | --primary-border-color:#8549a7;
57 |
58 | --button-solid-background-color:#fff;
59 | --button-solid-text-color:#000;
60 |
61 | --button-outlined-background-color:transparent;
62 | --button-outlined-text-color:#fff;
63 |
64 | }
65 |
66 |
67 | html, body, #root{
68 | padding: 0;
69 | margin: 0;
70 | height: 100%;
71 | width: 100%;
72 | }
73 |
74 | div{
75 | overflow-y: auto;
76 | }
77 |
78 | #app{
79 | height: 100%;
80 | background-color: var(--primary-background-color);
81 | color:var(--primary-text-color);
82 | transition: background-color 0.5s ease, color 0.5s ease;
83 |
84 | }
85 |
86 | #container{
87 | max-width: 600px;
88 | margin: 0em auto;
89 | padding: 2em 0;
90 | }
91 |
92 | #todo-form{
93 | margin: 2em 0;
94 | }
95 |
96 | #todo-form input{
97 | border: 1px solid var(--primary-border-color);
98 | padding: 1em;
99 | border-radius: 10px;
100 | width: 100%;
101 | font-size: 18px;
102 | box-sizing: border-box;
103 | background-color: transparent;
104 | color: var(--primary-text-color);
105 | }
106 |
107 | .note-wrapper{
108 | /* border-bottom:1px solid hsla(0, 1%,22%,1); */
109 | background-color: var(--secondary-background-color);
110 | padding: 1em;
111 | margin: 1em 0;
112 | border-radius: 5px;
113 | display: flex;
114 | justify-content: space-between;
115 | /* align-items: center; */
116 | }
117 |
118 | .note-body{
119 | cursor: pointer;
120 | }
121 |
122 | .delete{
123 | cursor: pointer;
124 | }
125 |
126 | .auth-field-wrapper{
127 |
128 | display: flex;
129 | flex-direction: column;
130 | margin: 1em 0 ;
131 |
132 |
133 | }
134 |
135 | .auth-field-wrapper input[type='text'], input[type='email'], input[type='password']{
136 | border: none;
137 | outline: none;
138 | background-color: transparent;
139 | height: 75px;
140 | box-sizing: border-box;
141 | border-bottom: 1px solid var(--primary-border-color);
142 | font-size: 18px;
143 | color: var(--button-outlined-text-color);
144 | }
145 |
146 | .auth-field-wrapper input[type='submit']{
147 | padding: 1em 1.5em;
148 |
149 | width: fit-content;
150 | font-size: 18px;
151 | font-weight: 500;
152 | border:0;
153 | border-radius: 50px;
154 | background-color: var(--button-solid-background-color);
155 | color: var(--button-solid-text-color);
156 | }
157 |
158 | button{
159 | padding: 1em 1.5em;
160 |
161 | width: fit-content;
162 | font-size: 18px;
163 | font-weight: 500;
164 | border:0;
165 | border-radius: 50px;
166 | border: 1.5px solid var(--button-outlined-text-color);
167 | background-color: transparent;
168 | color: var(--button-outlined-text-color);;
169 | }
170 |
171 | input[type="submit"], button{
172 | cursor: pointer;
173 | }
174 |
175 | #logout-btn{
176 | position: fixed;
177 | bottom: 2em;
178 | right: 2em;
179 | }
180 |
181 | .tag{
182 | background-color:#61b593 ;
183 | padding: 0.5em 1em;
184 | border-radius: 20px;
185 | width: fit-content;
186 | font-size: 12px;
187 | font-weight: 700;
188 | color: #fff;
189 | }
190 |
191 | #pro-header{
192 | display: flex;
193 | justify-content: space-between;
194 | align-items: center;
195 | }
196 |
197 | #subscribe-prompt{
198 | display: flex;
199 | align-items:center;
200 | gap: 0.5em;
201 | background-color: var(--alert-color);
202 | border: 1px solid var(--alert-border-color);
203 | padding: 0 1em;
204 | border-radius: 5px;
205 | color: var(--alert-text-color);
206 | cursor: pointer;
207 | transition: 0.3s;
208 | }
209 |
210 | #subscribe-prompt:hover{
211 | color: #886f23;
212 | background-color: #f0e4c1;
213 | }
214 |
215 | .theme-options{
216 | display: flex;
217 | gap:10px;
218 | }
219 |
220 | .theme-option{
221 | height: 25px;
222 | width: 25px;
223 | border: 1px solid;
224 | border-radius: 50%;
225 | cursor: pointer;
226 | }
227 |
228 |
229 |
230 | @media screen and (max-width:600px){
231 | #app{
232 | padding: 0 1em;
233 | }
234 | }
--------------------------------------------------------------------------------
/Part-3/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 |
--------------------------------------------------------------------------------
/Part-3/src/pages/LoginRegister.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import { useAuth } from "../context/AuthContext";
4 | import LoginForm from "../components/LoginForm";
5 | import RegisterFrom from "../components/RegisterFrom";
6 |
7 | const LoginRegister = () => {
8 | const navigate = useNavigate();
9 | const { user } = useAuth();
10 | const [activeForm, setActiveForm] = useState("login");
11 |
12 | useEffect(() => {
13 | if (user) {
14 | navigate("/");
15 | }
16 | });
17 |
18 | return activeForm === "login" ? (
19 |
20 | ) : (
21 |
22 | );
23 | };
24 | export default LoginRegister;
25 |
--------------------------------------------------------------------------------
/Part-3/src/pages/Notes.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import { Query } from "appwrite";
3 | import { Navigate } from "react-router-dom";
4 | import db from "../appwrite/databases";
5 | import NoteForm from "../components/NoteForm";
6 | import Note from "../components/Note";
7 | import LogoutButton from "../components/LogoutButton";
8 | import { useAuth } from "../context/AuthContext";
9 | import ThemeOption from "../components/ThemeOption";
10 | import LockIcon from "../assets/LockIcon";
11 |
12 | const Notes = () => {
13 | const [notes, setNotes] = useState([]);
14 | const [pro, setPro] = useState(false);
15 | const { user } = useAuth();
16 |
17 | useEffect(() => {
18 | init();
19 | }, []);
20 |
21 | const init = async () => {
22 | setPro(user.labels.includes("premium"));
23 | const response = await db.notes.list([Query.orderDesc("$createdAt")]);
24 | setNotes(response.documents);
25 | };
26 |
27 | return (
28 | <>
29 | {/* {pro ? (
30 |
44 | ) : (
45 |
46 |
47 |
Unlock purple mode for $1
48 |
49 | )} */}
50 |
51 |
61 |
62 |
✍️ My Todo List
63 |
64 |
65 |
66 |
67 | {notes.map((note) => (
68 |
69 | ))}
70 |
71 |
72 | >
73 | );
74 | };
75 |
76 | export default Notes;
77 |
--------------------------------------------------------------------------------
/Part-3/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Appwrite 🤝 React
2 |
3 | Appwrite + React fullstack todo app with integrated auth.
4 |
5 | ## Understanding Folder Structure
6 |
7 | This project is part of a multi part video series, so our folders are structured in a way to represent the code at the end of each video. i.e. `Video 1` == `Part 1`, and so on.
8 |
9 |
10 |
11 | Part
12 | Topic
13 | Description
14 | View
15 |
16 |
17 | 1
18 | CRUD
19 | Basic setup and CRUD with Appwrite + React
20 | View
21 |
22 |
23 | 2
24 | Theme Switcher
25 | Added theme switcher
26 | View
27 |
28 |
29 | 2
30 | Theme Switcher (Template Only)
31 | Theme switcher with no backend.
32 | View
33 |
34 |
35 | 3
36 | Authentication
37 | Login, Logout, Registration & Protected Routes
38 | View
39 |
40 |
41 |
42 | See tutorial part 1 here: https://youtu.be/_JDeJgsU-bI
43 |
44 |
45 |
46 | ## Setup instructions
47 |
48 | Before you can clone and setup a local instance of this project you'll need to setup an Appwrite backend and gain the nessesary credentials + have the correct database setup and permisisons configured.
49 |
50 | ### Setting up appwrite backend
51 |
52 | 1. Create a new appwrite project + app. Easiest way to get started is by heading to [appwrite.io](https://appwrite.io/)
53 |
54 | 2. Add a platform and set hostname registration
55 |
56 | This can be done by going to `overview` -> `Add Platform` -> `Web App`.
57 |
58 | 3. Add a user
59 |
60 | 4. Add a database and a collection
61 |
62 | Add the following attributes in your collection
63 |
64 |
65 |
66 | Name
67 | Type
68 | Details
69 |
70 |
71 | body
72 | string
73 | 100 chars
74 |
75 |
76 | completed
77 | boolean
78 | Default to false
79 |
80 |
81 |
82 | 5. Set collection perissions
83 |
84 | This can be done under from your collection tab unde `settings` -> `permissions`.
85 |
86 | Set permissions to `users` + `create`
87 |
88 | Enable document level security
89 |
90 | #### Setup local repo
91 |
92 | **Clone Repo**
93 | Clone repo: `git clone `
94 |
95 | Add `.env` file and fill in your Appwrite project credentials:
96 |
97 | ```
98 | VITE_PROJECT_ID=YOUR-PROJECT-ID
99 | VITE_DATABASE_ID=YOUR-DATABASE-ID
100 | VITE_COLLECTION_TASKS=YOUR-COLLECTION-ID
101 | ```
102 |
103 | - `cd `
104 | - `npm i` && `npm run dev`
105 |
--------------------------------------------------------------------------------
/assets/.env.example:
--------------------------------------------------------------------------------
1 | VITE_ENDPOINT=https://cloud.appwrite.io/v1
2 | VITE_PROJECT_ID=
3 | VITE_DATABASE_ID=
4 | VITE_COLLECTION_ID_NOTES=
--------------------------------------------------------------------------------
/assets/notes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/divanov11/Appwrite-React-quickstart/658881d728939d622e0f5ac9c4b8eb093a2cbcb2/assets/notes.png
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Appwrite-React-quickstart",
3 | "lockfileVersion": 3,
4 | "requires": true,
5 | "packages": {}
6 | }
7 |
--------------------------------------------------------------------------------