├── slides.pdf ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── images ├── deploy-1.png ├── deploy-2.png ├── deploy-3.png ├── deploy-4.png ├── deploy-5.png ├── deploy-6.png ├── deploy-7.png ├── deploy-8.png ├── 4b3_insert-row.png ├── 3c2_create-table.png └── 5b3_retrieve-rows.png ├── netlify.toml ├── src ├── index.js ├── Header.js ├── Todo.js ├── TodoTextInput.js ├── utils │ └── api.js ├── Footer.js ├── TodoList.js ├── App.js └── index.css ├── .vscode └── settings.json ├── links.md ├── .github └── ISSUE_TEMPLATE │ └── homework-assignment.md ├── .gitignore ├── functions ├── deleteRestTodo.js ├── createRestTodo.js ├── updateRestTodo.js ├── getRestTodos.js └── utils │ └── astraRestClient.js ├── .gitpod.yml ├── astra.json ├── package.json ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md └── README.md /slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/slides.pdf -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /images/deploy-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/deploy-1.png -------------------------------------------------------------------------------- /images/deploy-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/deploy-2.png -------------------------------------------------------------------------------- /images/deploy-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/deploy-3.png -------------------------------------------------------------------------------- /images/deploy-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/deploy-4.png -------------------------------------------------------------------------------- /images/deploy-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/deploy-5.png -------------------------------------------------------------------------------- /images/deploy-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/deploy-6.png -------------------------------------------------------------------------------- /images/deploy-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/deploy-7.png -------------------------------------------------------------------------------- /images/deploy-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/deploy-8.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/public/logo512.png -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | functions = "functions" 4 | publish = "build" 5 | 6 | -------------------------------------------------------------------------------- /images/4b3_insert-row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/4b3_insert-row.png -------------------------------------------------------------------------------- /images/3c2_create-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/3c2_create-table.png -------------------------------------------------------------------------------- /images/5b3_retrieve-rows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datastaxdevs/appdev-week1-todolist/main/images/5b3_retrieve-rows.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById("root") 11 | ); 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.editor.enablePreviewFromCodeNavigation": true, 3 | "workbench.editor.enablePreviewFromQuickOpen": true, 4 | "workbench.editor.enablePreview": true, 5 | "workbench.editorAssociations": { 6 | "*.md": "vscode.markdown.preview.editor" 7 | } 8 | } -------------------------------------------------------------------------------- /links.md: -------------------------------------------------------------------------------- 1 | Astra 2 | ``` bash 3 | https://dtsx.io/appdev-7-8 4 | ``` 5 | 6 | Repo 7 | ``` bash 8 | https://github.com/datastaxdevs/appdev-week1-todolist 9 | ``` 10 | 11 | React Repo 12 | ``` bash 13 | https://github.com/datastaxdevs/react-basics 14 | ``` 15 | 16 | Menti 17 | ``` bash 18 | Join @ menti.com! https://www.menti.com/ni6by99hiu 19 | ``` 20 | 21 | Discord 22 | ``` bash 23 | https://dtsx.io/discord 24 | ``` 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/homework-assignment.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Homework Assignment 3 | about: Use this template to submit the homework 4 | title: "[HW] " 5 | labels: homework, wait for review 6 | assignees: SonicDMG, RyanWelford 7 | 8 | --- 9 | 10 | **Name:** 11 | **Email:** 12 | **Linkedin Profile:** 13 | 14 | Attach the homework screenshots below: 15 | ----------------------------------------- 16 | 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | venv 4 | .env 5 | 6 | # dependencies 7 | /node_modules 8 | /.pnp 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # Local Netlify folder 29 | .netlify -------------------------------------------------------------------------------- /src/Header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TodoTextInput from "./TodoTextInput"; 3 | 4 | function Header (props) { 5 | const { title, addTodo } = props; 6 | 7 | const handleSave = (text) => { 8 | if (text.length !== 0) { 9 | addTodo(text); 10 | } 11 | }; 12 | const label = title; 13 | 14 | return ( 15 |
16 |

{label}

17 | 22 |
23 | ); 24 | } 25 | 26 | export default Header; 27 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /functions/deleteRestTodo.js: -------------------------------------------------------------------------------- 1 | const { getRestClient } = require("./utils/astraRestClient"); 2 | 3 | exports.handler = async (event, context) => { 4 | const body = JSON.parse(event.body); 5 | const client = await getRestClient(); 6 | try { 7 | let delete_path = '/api/rest/v2/keyspaces/todos/rest/' + body.id 8 | const res = await client.delete(delete_path); 9 | return { 10 | statusCode: 204, 11 | headers: { 12 | 'Content-Type': 'application/json' 13 | }, 14 | }; 15 | } catch (e) { 16 | return { 17 | statusCode: 400, 18 | body: JSON.stringify(e), 19 | }; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Astra • ReactJS • Todo Demo 10 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - name: appdev-week1-todolist 3 | init: | 4 | cd /workspace/appdev-week1-todolist 5 | nvm install node 6 | npm install -g npm@latest 7 | command: | 8 | cd /workspace/appdev-week1-todolist 9 | gp open README.md 10 | echo "appdev-week1-todolist gitpod ready - LET'S DO THIS!" 11 | npm install 12 | github: 13 | prebuilds: 14 | master: true 15 | branches: true 16 | pullRequests: true 17 | pullRequestsFromForks: false 18 | addCheck: true 19 | addComment: false 20 | addBadge: true 21 | addLabel: false 22 | ports: 23 | - port: 8888 24 | onOpen: open-preview 25 | - port: 3000 26 | onOpen: ignore 27 | -------------------------------------------------------------------------------- /functions/createRestTodo.js: -------------------------------------------------------------------------------- 1 | const { getRestClient } = require("./utils/astraRestClient"); 2 | exports.handler = async (event, context) => { 3 | const todos = await getRestClient(); 4 | const body = JSON.parse(event.body); 5 | event.body.key = "todo" 6 | 7 | const res = await todos.post('/api/rest/v2/keyspaces/todos/rest', event.body); 8 | if (res.status == 201) { 9 | return { 10 | statusCode: res.status, 11 | body: JSON.stringify(res.data), 12 | headers: { 13 | 'Content-Type': 'application/json' 14 | }, 15 | } 16 | } else { 17 | return { 18 | statusCode: res.status, 19 | body: JSON.stringify(res.data) 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /functions/updateRestTodo.js: -------------------------------------------------------------------------------- 1 | const { getRestClient } = require("./utils/astraRestClient"); 2 | 3 | exports.handler = async (event, context) => { 4 | const todos = await getRestClient(); 5 | let body = JSON.parse(event.body); 6 | 7 | try { 8 | let path = '/api/rest/v2/keyspaces/todos/rest/' + body.id; 9 | body = {"text":body.text, "completed":body.completed} 10 | const res = await todos.put(path, body); 11 | return { 12 | statusCode: res.status, 13 | body: JSON.stringify(res), 14 | headers: { 15 | 'Content-Type': 'application/json' 16 | }, 17 | }; 18 | } catch (e) { 19 | return { 20 | statusCode: 400, 21 | body: JSON.stringify(e), 22 | }; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /src/Todo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classnames from "classnames"; 3 | 4 | function Todo (props) { 5 | const { todo, completeRestTodo, deleteRestTodo } = props; 6 | 7 | return ( 8 |
  • 13 | 14 |
    15 | completeRestTodo(todo.id, todo.text, todo.completed)} 20 | /> 21 | 22 |
    24 |
  • 25 | ); 26 | } 27 | 28 | export default Todo; 29 | -------------------------------------------------------------------------------- /astra.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ToDo App using JAMStack, Netlify, and AstraDB", 3 | "description": "This is an example React To-Do application using a DataStax Astra free tier database.", 4 | "duration": "10 minutes", 5 | "skillLevel": "Beginner", 6 | "heroImage": "https://camo.githubusercontent.com/fae2f66007b221f99ded30bc5edf9bfe4f2046d4d7c426dede44d66cb0588c58/68747470733a2f2f6d6f6e6f736e61702e636f6d2f696d6167652f4676307950417a6e62654e4a443376596c51667a744d4536796f677a4654", 7 | "githubUrl": "https://github.com/DataStax-Examples/todo-astra-jamstack-netlify", 8 | "gitpodUrl": "https://dtsx.io/3gtADSq", 9 | "netlifyUrl": "https://dtsx.io/2EtfGty", 10 | "tags": [{ "name": "javascript" }, { "name": "REST API" }], 11 | "category": "apps", 12 | "priority": 2 13 | } 14 | -------------------------------------------------------------------------------- /functions/getRestTodos.js: -------------------------------------------------------------------------------- 1 | const { getRestClient, requestWithRetry, wait } = require("./utils/astraRestClient"); 2 | 3 | exports.handler = async (event, context) => { 4 | const client = await getClient(); 5 | let res; 6 | try { 7 | res = await client.get('/api/rest/v2/keyspaces/todos/rest?where=\{"key":\{"$eq":\"rest"\}\}') 8 | const formattedTodos = Object.keys(res.data).map((item) => res.data[item]); 9 | return { 10 | headers: '{Content-Type: application/json}', 11 | statusCode: 200, 12 | body: JSON.stringify(formattedTodos), 13 | headers: { 14 | 'Content-Type': 'application/json' 15 | }, 16 | }; 17 | } catch (e) { 18 | return { 19 | statusCode: 400, 20 | body: JSON.stringify(e), 21 | }; 22 | } 23 | }; 24 | 25 | async function getClient() { 26 | let client = await getRestClient(); 27 | if (client === null) { 28 | wait(1000) 29 | return getClient() 30 | } 31 | return client 32 | } -------------------------------------------------------------------------------- /src/TodoTextInput.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classnames from "classnames"; 3 | 4 | function TodoTextInput (props) { 5 | const { newTodo, placeholder, onSave } = props; 6 | const [ text, setText ] = React.useState(""); 7 | 8 | 9 | const handleSubmit = (e) => { 10 | const text = e.target.value.trim(); 11 | if (e.which === 13) { 12 | onSave(text); 13 | if (newTodo) { 14 | setText(""); 15 | } 16 | } 17 | }; 18 | 19 | const handleChange = (e) => setText(e.target.value); 20 | 21 | const handleBlur = (e) => { 22 | if (!newTodo) { 23 | onSave(e.target.value); 24 | } 25 | }; 26 | 27 | return ( 28 | 40 | ); 41 | } 42 | 43 | export default TodoTextInput; -------------------------------------------------------------------------------- /src/utils/api.js: -------------------------------------------------------------------------------- 1 | // CREATE 2 | const addRestTodo = async (todo) => { 3 | const stringifiedBody = JSON.stringify(todo); 4 | const response = await fetch("/.netlify/functions/createRestTodo", { 5 | body: stringifiedBody, 6 | method: "POST", 7 | }); 8 | 9 | return response; 10 | }; 11 | 12 | // READ 13 | const getRestTodos = async () => { 14 | const response = await fetch(`/.netlify/functions/getRestTodos`); 15 | let todos = await response.json(); 16 | 17 | return todos.length ? todos : []; 18 | }; 19 | 20 | // UPDATE 21 | const updateRestTodo = async (todo) => { 22 | const stringifiedBody = JSON.stringify(todo); 23 | const response = await fetch("/.netlify/functions/updateRestTodo", { 24 | body: stringifiedBody, 25 | method: "PUT", 26 | }); 27 | 28 | let responsejson = await response.json(); 29 | return responsejson; 30 | }; 31 | 32 | // DELETE 33 | const deleteRestTodo = async (id) => { 34 | const stringifiedBody = JSON.stringify({ id }); 35 | const response = await fetch("/.netlify/functions/deleteRestTodo", { 36 | body: stringifiedBody, 37 | method: "DELETE", 38 | }); 39 | 40 | return response; 41 | }; 42 | 43 | const default_export = { 44 | getRestTodos, 45 | addRestTodo, 46 | deleteRestTodo, 47 | updateRestTodo 48 | }; 49 | 50 | export default default_export; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tmp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@astrajs/collections": "0.0.14", 7 | "@astrajs/rest": "^0.0.14", 8 | "@testing-library/jest-dom": "^5.11.4", 9 | "@testing-library/react": "^11.1.0", 10 | "@testing-library/user-event": "^13.1.8", 11 | "classnames": "^2.2.6", 12 | "fetch-with-proxy": "^3.0.1", 13 | "node-uuid": "^1.4.8", 14 | "npx": "^10.2.2", 15 | "react": "^17.0.2", 16 | "react-dom": "^17.0.2", 17 | "react-scripts": "4.0.3", 18 | "todomvc-app-css": "^2.0.6" 19 | }, 20 | "engine": { 21 | "npm": "^7.0.0" 22 | }, 23 | "scripts": { 24 | "dev": "netlify dev", 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "devDependencies": { 49 | "netlify-cli": "^3.28.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import classnames from "classnames"; 3 | 4 | const FILTER_TITLES = { 5 | SHOW_ALL: "All", 6 | SHOW_ACTIVE: "Active", 7 | SHOW_COMPLETED: "Completed", 8 | }; 9 | 10 | function Footer (props) { 11 | const { activeCount } = props; 12 | 13 | const renderTodoCount = () => { 14 | const itemWord = activeCount === 1 ? "item" : "items"; 15 | 16 | return ( 17 | 18 | {activeCount || "No"}  19 | {itemWord} left 20 | 21 | ); 22 | } 23 | 24 | const renderFilterLink = (filter) => { 25 | const title = FILTER_TITLES[filter]; 26 | const { filter: selectedFilter, onShow } = props; 27 | 28 | return ( 29 | 31 | ); 32 | } 33 | 34 | const renderFilterList = () => { 35 | return ["SHOW_ALL", "SHOW_ACTIVE", "SHOW_COMPLETED"].map((filter) => ( 36 |
  • {renderFilterLink(filter)}
  • 37 | )); 38 | } 39 | 40 | useEffect(() => { 41 | console.log("PROP Change: Active items is %d", activeCount); 42 | // eslint-disable-next-line react-hooks/exhaustive-deps 43 | }, [activeCount]); 44 | 45 | return ( 46 |
    47 | {renderTodoCount()} 48 |
      {renderFilterList()}
    49 |
    50 | ); 51 | } 52 | 53 | export default Footer; -------------------------------------------------------------------------------- /src/TodoList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Todo from "./Todo"; 3 | import Footer from "./Footer"; 4 | 5 | const TODO_FILTERS = { 6 | SHOW_ALL: () => true, 7 | SHOW_ACTIVE: (todo) => !todo.completed, 8 | SHOW_COMPLETED: (todo) => todo.completed, 9 | }; 10 | 11 | function TodoList (props) { 12 | const { actions, todos } = props; 13 | const [filter, setFilter] = React.useState("SHOW_ALL"); 14 | 15 | const handleShow = (filter) => { 16 | setFilter(filter); 17 | }; 18 | 19 | const handleClearCompletedDoc = () => { 20 | actions.clearCompletedDoc(); 21 | }; 22 | 23 | const renderFooter = (completedCount) => { 24 | const activeCount = todos.length - completedCount; 25 | 26 | if (todos.length) { 27 | return ( 28 |