From 60c304f4f52d4f08a2509aa835b2fbba28690ec5 Mon Sep 17 00:00:00 2001
From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com>
Date: Wed, 18 Dec 2024 11:36:43 -0600
Subject: [PATCH 01/10] unskipped test
---
src/components/Task.test.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/Task.test.jsx b/src/components/Task.test.jsx
index 3d4b5d03..6fd48b97 100644
--- a/src/components/Task.test.jsx
+++ b/src/components/Task.test.jsx
@@ -19,7 +19,7 @@ describe('Task', () => {
expect(screen.getByText('Test Title')).toBeInTheDocument();
});
- test.skip('Runs callbacks when buttons clicked', () => {
+ test('Runs callbacks when buttons clicked', () => {
// Arrange
const clickCallback = vi.fn();
const deleteCallback = vi.fn();
From 806dcbe8aee2ba37686dd354fa37494747b248fc Mon Sep 17 00:00:00 2001
From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com>
Date: Wed, 18 Dec 2024 11:37:25 -0600
Subject: [PATCH 02/10] Update the toggle complete feature of each Task to
update the state of the task data stored in App
---
src/App.jsx | 14 +++++++++++++-
src/components/Task.jsx | 10 ++++------
src/components/TaskList.jsx | 4 +++-
3 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/App.jsx b/src/App.jsx
index 899cfa00..f63890fb 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,6 @@
import TaskList from './components/TaskList.jsx';
import './App.css';
+import { useState } from 'react';
const TASKS = [
{
@@ -15,13 +16,24 @@ const TASKS = [
];
const App = () => {
+ const [taskData, setTaskData] = useState(TASKS);
+ const toggleComplete = (id) => {
+ setTaskData(taskData => taskData.map(task => {
+ if (task.id == id) {
+ return { ...task, isComplete: !task.isComplete };
+ } else {
+ return task;
+ }
+ }));
+ };
+
return (
);
diff --git a/src/components/Task.jsx b/src/components/Task.jsx
index a687076c..f84386ff 100644
--- a/src/components/Task.jsx
+++ b/src/components/Task.jsx
@@ -1,17 +1,14 @@
-import { useState } from 'react';
import PropTypes from 'prop-types';
-
import './Task.css';
-const Task = ({ id, title, isComplete }) => {
- const [complete, setComplete] = useState(isComplete);
- const buttonClass = complete ? 'tasks__item__toggle--completed' : '';
+const Task = ({ id, title, isComplete, onToggleComplete }) => {
+ const buttonClass = isComplete ? 'tasks__item__toggle--completed' : '';
return (
@@ -24,6 +21,7 @@ Task.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
isComplete: PropTypes.bool.isRequired,
+ onToggleComplete: PropTypes.func.isRequired,
};
export default Task;
diff --git a/src/components/TaskList.jsx b/src/components/TaskList.jsx
index 199ef575..075c6fce 100644
--- a/src/components/TaskList.jsx
+++ b/src/components/TaskList.jsx
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import Task from './Task.jsx';
import './TaskList.css';
-const TaskList = ({ tasks }) => {
+const TaskList = ({ tasks, onToggleComplete }) => {
const getTaskListJSX = (tasks) => {
return tasks.map((task) => {
return (
@@ -11,6 +11,7 @@ const TaskList = ({ tasks }) => {
id={task.id}
title={task.title}
isComplete={task.isComplete}
+ onToggleComplete={onToggleComplete}
/>
);
});
@@ -26,6 +27,7 @@ TaskList.propTypes = {
isComplete: PropTypes.bool.isRequired,
})
).isRequired,
+ onToggleComplete: PropTypes.func.isRequired,
};
export default TaskList;
From df84a88369ae1f7dc5b2b4e9b93b61699d0eda46 Mon Sep 17 00:00:00 2001
From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com>
Date: Wed, 18 Dec 2024 11:46:46 -0600
Subject: [PATCH 03/10] Added a feature to delete a task from the task data
stored and rendered by the App
---
src/App.jsx | 7 ++++++-
src/components/Task.jsx | 5 +++--
src/components/TaskList.jsx | 4 +++-
3 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/App.jsx b/src/App.jsx
index f63890fb..e1959357 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -26,6 +26,11 @@ const App = () => {
}
}));
};
+ const deleteTask = (id) => {
+ setTaskData(taskData => taskData.filter(task => {
+ return task.id !== id;
+ }));
+ };
return (
@@ -33,7 +38,7 @@ const App = () => {
Ada's Task List
- {}
+ {}
);
diff --git a/src/components/Task.jsx b/src/components/Task.jsx
index f84386ff..745a10fc 100644
--- a/src/components/Task.jsx
+++ b/src/components/Task.jsx
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import './Task.css';
-const Task = ({ id, title, isComplete, onToggleComplete }) => {
+const Task = ({ id, title, isComplete, onToggleComplete, onDeleteTask }) => {
const buttonClass = isComplete ? 'tasks__item__toggle--completed' : '';
return (
@@ -12,7 +12,7 @@ const Task = ({ id, title, isComplete, onToggleComplete }) => {
>
{title}
-
+
);
};
@@ -22,6 +22,7 @@ Task.propTypes = {
title: PropTypes.string.isRequired,
isComplete: PropTypes.bool.isRequired,
onToggleComplete: PropTypes.func.isRequired,
+ onDeleteTask: PropTypes.func.isRequired,
};
export default Task;
diff --git a/src/components/TaskList.jsx b/src/components/TaskList.jsx
index 075c6fce..13ad4a2a 100644
--- a/src/components/TaskList.jsx
+++ b/src/components/TaskList.jsx
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import Task from './Task.jsx';
import './TaskList.css';
-const TaskList = ({ tasks, onToggleComplete }) => {
+const TaskList = ({ tasks, onToggleComplete, onDeleteTask }) => {
const getTaskListJSX = (tasks) => {
return tasks.map((task) => {
return (
@@ -12,6 +12,7 @@ const TaskList = ({ tasks, onToggleComplete }) => {
title={task.title}
isComplete={task.isComplete}
onToggleComplete={onToggleComplete}
+ onDeleteTask={onDeleteTask}
/>
);
});
@@ -28,6 +29,7 @@ TaskList.propTypes = {
})
).isRequired,
onToggleComplete: PropTypes.func.isRequired,
+ onDeleteTask: PropTypes.func.isRequired,
};
export default TaskList;
From f25504e720098944d0b5b561f95c74b98e887b0c Mon Sep 17 00:00:00 2001
From: Natalie
Date: Wed, 18 Dec 2024 21:51:52 -0800
Subject: [PATCH 04/10] onToggleComplete and onDeleteTask
---
src/App.jsx | 72 +++++++++++++++++++++++++++++--------
src/components/Task.jsx | 26 ++++++++------
src/components/TaskList.jsx | 9 +++--
3 files changed, 80 insertions(+), 27 deletions(-)
diff --git a/src/App.jsx b/src/App.jsx
index 899cfa00..7655b05e 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,27 +1,71 @@
import TaskList from './components/TaskList.jsx';
import './App.css';
-
-const TASKS = [
- {
- id: 1,
- title: 'Mow the lawn',
- isComplete: false,
- },
- {
- id: 2,
- title: 'Cook Pasta',
- isComplete: true,
- },
-];
+import { useState, useEffect } from 'react';
+import axios from 'axios';
const App = () => {
+ const [tasks, setTasks] = useState([]);
+ const API_BASE_URL = 'http://127.0.0.1:5000';
+
+ useEffect(() => {
+ axios
+ .get(`${API_BASE_URL}/tasks`)
+ .then((response) => {
+ const formattedTasks = response.data.map((task) => ({
+ id: task.id,
+ title: task.title,
+ isComplete: task.completed_at !== null,
+ }));
+ setTasks(formattedTasks);
+ })
+ .catch((error) => {
+ console.error('Error fetching tasks:', error);
+ });
+ }, []);
+
+ const toggleCompleteTask = (taskId, isComplete) => {
+ const endpoint = isComplete
+ ? `${API_BASE_URL}/tasks/${taskId}/mark_incomplete`
+ : `${API_BASE_URL}/tasks/${taskId}/mark_complete`;
+
+ axios
+ .patch(endpoint)
+ .then(() => {
+ setTasks((prevTasks) =>
+ prevTasks.map((task) =>
+ task.id === taskId ? { ...task, isComplete: !isComplete } : task
+ )
+ );
+ })
+ .catch((error) => {
+ console.error('Error toggling task completion:', error);
+ });
+ };
+
+ const deleteTask = (taskId) => {
+ axios
+ .delete(`${API_BASE_URL}/tasks/${taskId}`)
+ .then(() => {
+ setTasks((prevTasks) => prevTasks.filter((task) => task.id !== taskId));
+ })
+ .catch((error) => {
+ console.error('Error deleting task:', error);
+ });
+ };
+
return (
);
diff --git a/src/components/Task.jsx b/src/components/Task.jsx
index a687076c..f3d1cb0d 100644
--- a/src/components/Task.jsx
+++ b/src/components/Task.jsx
@@ -1,21 +1,23 @@
-import { useState } from 'react';
import PropTypes from 'prop-types';
-
import './Task.css';
-const Task = ({ id, title, isComplete }) => {
- const [complete, setComplete] = useState(isComplete);
- const buttonClass = complete ? 'tasks__item__toggle--completed' : '';
-
+const Task = ({ id, title, isComplete, onToggleComplete, onDeleteTask }) => {
return (
-
-
onDeleteTask(id)}
>
- Delete
+ x
);
From 39c9c080f9356795c85f0da14b73a2d989aae0fd Mon Sep 17 00:00:00 2001
From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com>
Date: Sat, 4 Jan 2025 06:31:22 -0600
Subject: [PATCH 06/10] implemented handleSubmit for form submission to create
task, convertFromApi to correctly format data from API response
---
src/App.jsx | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/App.jsx b/src/App.jsx
index e9c90dfe..e7460622 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,4 +1,5 @@
import TaskList from './components/TaskList.jsx';
+import NewTaskForm from './components/NewTaskForm.jsx';
import './App.css';
import { useState, useEffect } from 'react';
import axios from 'axios';
@@ -53,12 +54,34 @@ const App = () => {
});
};
+ const convertFromApi = (apiTask) => {
+ const newTask = {
+ ...apiTask,
+ isComplete: apiTask.is_complete,
+ };
+ delete newTask.is_complete;
+ return newTask;
+ };
+
+ const handleSubmit = (taskData) => {
+ axios.post(`${API_BASE_URL}/tasks`, taskData)
+ .then((result) => {
+ setTasks((prevTasks) => [convertFromApi(result.data.task), ...prevTasks]);
+ })
+ .catch((error) => console.log(error));
+ };
+
return (
+
+
+
Date: Sat, 4 Jan 2025 06:31:47 -0600
Subject: [PATCH 07/10] form component
---
src/components/NewTaskForm.jsx | 47 ++++++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
create mode 100644 src/components/NewTaskForm.jsx
diff --git a/src/components/NewTaskForm.jsx b/src/components/NewTaskForm.jsx
new file mode 100644
index 00000000..0a9860e8
--- /dev/null
+++ b/src/components/NewTaskForm.jsx
@@ -0,0 +1,47 @@
+import { useState } from 'react';
+import PropTypes from 'prop-types';
+
+const NewTaskForm = ({ handleSubmit }) => {
+ const kDefaultFormState = {
+ title: '',
+ description: '',
+ isComplete: false,
+ };
+
+ const [formData, setFormData] = useState(kDefaultFormState);
+
+ const handleChange = event => {
+ const fieldName = event.target.name;
+ const fieldValue = event.target.value;
+ const newFormData = {...formData, [fieldName]: fieldValue};
+ setFormData(newFormData);
+ };
+
+ const onHandleSubmit = (event) => {
+ event.preventDefault();
+ handleSubmit(formData);
+ setFormData(kDefaultFormState);
+ };
+
+ return (
+
+ );
+};
+
+NewTaskForm.propTypes = {
+ handleSubmit: PropTypes.func.isRequired,
+};
+
+export default NewTaskForm;
\ No newline at end of file
From f974541f4b0e87e5078cc7a10663f83c7d9bb215 Mon Sep 17 00:00:00 2001
From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com>
Date: Mon, 6 Jan 2025 09:09:54 -0600
Subject: [PATCH 08/10] .env update
---
.env | 1 +
.gitignore | 2 --
src/App.jsx | 2 +-
3 files changed, 2 insertions(+), 3 deletions(-)
create mode 100644 .env
diff --git a/.env b/.env
new file mode 100644
index 00000000..ea551cd4
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+API_BASE_URL=http://localhost:5000
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index d7de12f3..a547bf36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,8 +12,6 @@ dist
dist-ssr
*.local
-.env
-
# Editor directories and files
.vscode/*
!.vscode/extensions.json
diff --git a/src/App.jsx b/src/App.jsx
index e7460622..68582f3f 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -6,7 +6,7 @@ import axios from 'axios';
const App = () => {
const [tasks, setTasks] = useState([]);
- const API_BASE_URL = 'http://127.0.0.1:5000';
+ const API_BASE_URL = import.meta.env.API_BASE_URL;
useEffect(() => {
axios
From c571be9ea4520e7d87bd55801a4ebe11b50fc766 Mon Sep 17 00:00:00 2001
From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com>
Date: Mon, 6 Jan 2025 09:16:37 -0600
Subject: [PATCH 09/10] updated test
---
src/components/Task.test.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/Task.test.jsx b/src/components/Task.test.jsx
index 6fd48b97..3d4b5d03 100644
--- a/src/components/Task.test.jsx
+++ b/src/components/Task.test.jsx
@@ -19,7 +19,7 @@ describe('Task', () => {
expect(screen.getByText('Test Title')).toBeInTheDocument();
});
- test('Runs callbacks when buttons clicked', () => {
+ test.skip('Runs callbacks when buttons clicked', () => {
// Arrange
const clickCallback = vi.fn();
const deleteCallback = vi.fn();
From a8a185516b4b2b18454f64d570470d9fc89a65a1 Mon Sep 17 00:00:00 2001
From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com>
Date: Mon, 6 Jan 2025 09:22:05 -0600
Subject: [PATCH 10/10] updated, .env
---
.env | 2 +-
package-lock.json | 8 ++++----
package.json | 2 +-
src/App.jsx | 2 +-
4 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/.env b/.env
index ea551cd4..f95c81f1 100644
--- a/.env
+++ b/.env
@@ -1 +1 @@
-API_BASE_URL=http://localhost:5000
\ No newline at end of file
+VITE_APP_BACKEND_URL=http://localhost:5000
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index fb50dd28..b8f8182f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
"name": "npm-task",
"version": "0.0.0",
"dependencies": {
- "axios": "^1.7.7",
+ "axios": "^1.7.9",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
@@ -1887,9 +1887,9 @@
}
},
"node_modules/axios": {
- "version": "1.7.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
- "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+ "version": "1.7.9",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
diff --git a/package.json b/package.json
index 0b121eef..e4727ed7 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,7 @@
"test": "vitest"
},
"dependencies": {
- "axios": "^1.7.7",
+ "axios": "^1.7.9",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
diff --git a/src/App.jsx b/src/App.jsx
index 68582f3f..1e1e998f 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -6,7 +6,7 @@ import axios from 'axios';
const App = () => {
const [tasks, setTasks] = useState([]);
- const API_BASE_URL = import.meta.env.API_BASE_URL;
+ const API_BASE_URL = import.meta.env.VITE_APP_BACKEND_URL;
useEffect(() => {
axios