diff --git a/package-lock.json b/package-lock.json index 1ac1f0d..30fa57f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,12 @@ "name": "todo-react", "version": "0.1.0", "dependencies": { + "dotenv": "^16.3.1", + "fs": "^0.0.1-security", + "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.21.0", "react-scripts": "5.0.1" } }, @@ -3218,6 +3222,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.0.tgz", + "integrity": "sha512-WOHih+ClN7N8oHk9N4JUiMxQJmRVaOxcg8w7F/oHUXzJt920ekASLI/7cYX8XkntDWRhLZtsk6LbGrkgOAvi5A==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -6351,11 +6363,14 @@ } }, "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/dotenv-expand": { @@ -7867,6 +7882,11 @@ "node": ">= 0.6" } }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -13965,6 +13985,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.0.tgz", + "integrity": "sha512-hGZ0HXbwz3zw52pLZV3j3+ec+m/PQ9cTpBvqjFQmy2XVUWGn5MD+31oXHb6dVTxYzmAeaiUBYjkoNz66n3RGCg==", + "dependencies": { + "@remix-run/router": "1.14.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.21.0.tgz", + "integrity": "sha512-1dUdVj3cwc1npzJaf23gulB562ESNvxf7E4x8upNJycqyUm5BRRZ6dd3LrlzhtLaMrwOCO8R0zoiYxdaJx4LlQ==", + "dependencies": { + "@remix-run/router": "1.14.0", + "react-router": "6.21.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -14037,6 +14087,14 @@ } } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 1420890..5cbc295 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,12 @@ "version": "0.1.0", "private": true, "dependencies": { + "dotenv": "^16.3.1", + "fs": "^0.0.1-security", + "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.21.0", "react-scripts": "5.0.1" }, "scripts": { diff --git a/src/App.js b/src/App.js index db95f95..841d10c 100644 --- a/src/App.js +++ b/src/App.js @@ -1,47 +1,91 @@ -import React, { useEffect, useState } from 'react'; -import AddTodoForm from './AddTodoForm'; -import TodoList from './TodoList'; +import { useEffect, useState } from 'react'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import AddTodoForm from './components/AddTodoForm'; +import TodoList from './components/TodoList'; + +function App() { + + const [todoList, setTodoList] = useState([]) + const [isLoading, setIsLoading] = useState(true) -const useSemiPersistentState = () => { - const [todoList, setTodoList] = useState(JSON.parse(localStorage.getItem("savedTodoList"))??[]) - - useEffect(() => { - localStorage.setItem("savedTodoList", JSON.stringify(todoList)) - }, [todoList]) - return [todoList, setTodoList] + const fetchData = async () => { + const url = `https://api.airtable.com/v0/${process.env.REACT_APP_AIRTABLE_BASE_ID}/${process.env.REACT_APP_TABLE_NAME}` + + const options = { + method: 'GET', + headers: { + Authorization:`Bearer ${process.env.REACT_APP_AIRTABLE_API_TOKEN}` + } + } + + try { + const response = await fetch(url, options) + if(!response.ok) { + throw new Error(`Error: ${response.status}`) + } + const data = await response.json() + + const todos = data.records.map((todo) => todo = {title: todo.fields.title, id: todo.id }) + setTodoList(todos) + setIsLoading(false) + } catch (error) { + console.log(error) + } + } -function App() { + useEffect(() => { + fetchData(); + }, []) + + + useEffect(() => { + if (!isLoading) { + localStorage.setItem("savedTodoList", JSON.stringify(todoList)) + } + }, [isLoading, todoList]) - const [value, setValue] = useSemiPersistentState() const addTodo = (newTodo) => { - setValue([...value, newTodo]) + setTodoList([...todoList, newTodo]) } const removeTodo = (id) => { - const index = value.findIndex(todo => todo.id === id) + const index = todoList.findIndex(todo => todo.id === id) if(index !== -1) { - const updatedList = [...value] + const updatedList = [...todoList] updatedList.splice(index, 1) - setValue(updatedList) + setTodoList(updatedList) } } return ( - <> -
-

Todo List

-
- - - +
+ + + +
+

Todo List

+
+ + {isLoading ? ( +

Loading...

+ ) : ( + + )} + } /> + +

New Todo List

+ } /> +
+
+
); } diff --git a/src/InputWithLabel.js b/src/InputWithLabel.js deleted file mode 100644 index d5149d6..0000000 --- a/src/InputWithLabel.js +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect, useRef } from "react"; - -const InputWithLabel = (props) => { - - const inputRef = useRef(); - - useEffect(() => { - inputRef.current.focus() - }) - - return ( - <> - - - - ) -} - -export default InputWithLabel diff --git a/src/TodoListItem.js b/src/TodoListItem.js deleted file mode 100644 index 8c9b347..0000000 --- a/src/TodoListItem.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; - -const TodoListItem = ({ todo, onRemoveTodo }) => { - - return ( -
  • - {todo.title} - - -
  • - ) -} - -export default TodoListItem diff --git a/src/AddTodoForm.js b/src/components/AddTodoForm.js similarity index 74% rename from src/AddTodoForm.js rename to src/components/AddTodoForm.js index b1fc031..5880d3c 100644 --- a/src/AddTodoForm.js +++ b/src/components/AddTodoForm.js @@ -1,5 +1,7 @@ import React, { useState } from "react"; import InputWithLabel from "./InputWithLabel"; +import style from './AddTodoForm.module.css' +import PropTypes from 'prop-types' const AddTodoForm = ({ onAddTodo }) => { @@ -20,13 +22,17 @@ const AddTodoForm = ({ onAddTodo }) => { } return ( -
    +
    Title - +
    ) } +AddTodoForm.prototype = { + onAddTodo: PropTypes.func +} + export default AddTodoForm; \ No newline at end of file diff --git a/src/components/AddTodoForm.module.css b/src/components/AddTodoForm.module.css new file mode 100644 index 0000000..4fd2fd5 --- /dev/null +++ b/src/components/AddTodoForm.module.css @@ -0,0 +1,9 @@ +.divForm { + display: flex; + justify-content: center; + margin-bottom: 1%; +} + +.addButton { + background-color: white; +} \ No newline at end of file diff --git a/src/components/InputWithLabel.js b/src/components/InputWithLabel.js new file mode 100644 index 0000000..6432145 --- /dev/null +++ b/src/components/InputWithLabel.js @@ -0,0 +1,27 @@ +import { useEffect, useRef } from "react"; +import PropTypes from 'prop-types' + +const InputWithLabel = (props) => { + + const inputRef = useRef(); + + useEffect(() => { + inputRef.current.focus() + }) + + return ( + <> + + + + ) +} + +InputWithLabel.prototype = { + todoTitle: PropTypes.node, + handleTitleChange: PropTypes.node, + children: PropTypes.node + +} + +export default InputWithLabel diff --git a/src/TodoList.js b/src/components/TodoList.js similarity index 66% rename from src/TodoList.js rename to src/components/TodoList.js index 32428ad..40732cc 100644 --- a/src/TodoList.js +++ b/src/components/TodoList.js @@ -1,11 +1,13 @@ import React from "react"; import TodoListItem from "./TodoListItem"; +import style from './TodoList.module.css' +import PropTypes from 'prop-types' const TodoList = ({ todoState, onRemoveTodo }) => { return ( -
    +
      {todoState?.map((todo) => { return ( @@ -17,4 +19,9 @@ const TodoList = ({ todoState, onRemoveTodo }) => { ) } +TodoList.prototype = { + todoState: PropTypes.array, + onRemoveTodo: PropTypes.func +} + export default TodoList \ No newline at end of file diff --git a/src/components/TodoList.module.css b/src/components/TodoList.module.css new file mode 100644 index 0000000..282b5ef --- /dev/null +++ b/src/components/TodoList.module.css @@ -0,0 +1,4 @@ +.divTodoList { + display: flex; + justify-content: center; +} \ No newline at end of file diff --git a/src/components/TodoListItem.js b/src/components/TodoListItem.js new file mode 100644 index 0000000..013311a --- /dev/null +++ b/src/components/TodoListItem.js @@ -0,0 +1,20 @@ +import React from "react"; +import style from "./TodoListItem.module.css" +import PropTypes from 'prop-types' + +const TodoListItem = ({ todo, onRemoveTodo }) => { + + return ( +
    • + {todo.title + ' '} + +
    • + ) +} + +TodoListItem.prototype = { + todo: PropTypes.string, + onRemoveTodo: PropTypes.func +} + +export default TodoListItem diff --git a/src/components/TodoListItem.module.css b/src/components/TodoListItem.module.css new file mode 100644 index 0000000..fff1a92 --- /dev/null +++ b/src/components/TodoListItem.module.css @@ -0,0 +1,8 @@ +.listItem { + color: blue !important; + margin-bottom: 10%; +} + +.removeButton { + background-color: white; +} \ No newline at end of file