Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_APP_BACKEND_URL=http://localhost:5000
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ dist
dist-ssr
*.local

.env

# Editor directories and files
.vscode/*
!.vscode/extensions.json
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"test": "vitest"
},
"dependencies": {
"axios": "^1.7.7",
"axios": "^1.7.9",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
95 changes: 81 additions & 14 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,94 @@
import TaskList from './components/TaskList.jsx';
import NewTaskForm from './components/NewTaskForm.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 = import.meta.env.VITE_APP_BACKEND_URL;

useEffect(() => {
axios
.get(`${API_BASE_URL}/tasks`)
.then((response) => {
const formattedTasks = response.data.map((task) => ({
id: task.id,
title: task.title,
isComplete: task.is_complete,
}));
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);
});
};

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 (
<div className="App">
<header className="App-header">
<h1>Ada&apos;s Task List</h1>
</header>
<main>
<div>{<TaskList tasks={TASKS} />}</div>
<div>
<NewTaskForm
handleSubmit={handleSubmit}
/>
</div>
<div>
<TaskList
tasks={tasks}
onToggleComplete={toggleCompleteTask}
onDeleteTask={deleteTask}
/>
</div>
</main>
</div>
);
Expand Down
47 changes: 47 additions & 0 deletions src/components/NewTaskForm.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<form onSubmit={onHandleSubmit}>
<div>
<label htmlFor="title">Title: </label>
<input type="text" id="title" name="title" value={formData.title} onChange={handleChange}/>
</div>
<div>
<label htmlFor="description">Description: </label>
<input type="text" id="description" name="description" value={formData.description} onChange={handleChange}/>
</div>
<div>
<input type="submit" value="Add a task"/>
</div>
</form>
);
};

NewTaskForm.propTypes = {
handleSubmit: PropTypes.func.isRequired,
};

export default NewTaskForm;
22 changes: 13 additions & 9 deletions src/components/Task.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<li className="tasks__item">
<button
className={`tasks__item__toggle ${buttonClass}`}
onClick={() => setComplete(!complete)}
className={`tasks__item__toggle ${
isComplete ? 'tasks__item__toggle--completed' : ''
}`}
onClick={() => onToggleComplete(id, isComplete)}
>
{title}
</button>
<button className="tasks__item__remove button">x</button>
<button
className="tasks__item__remove button"
onClick={() => onDeleteTask(id)}
>
x
</button>
</li>
);
};
Expand All @@ -24,6 +26,8 @@ Task.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
isComplete: PropTypes.bool.isRequired,
onToggleComplete: PropTypes.func.isRequired,
onDeleteTask: PropTypes.func.isRequired,
};

export default Task;
9 changes: 7 additions & 2 deletions src/components/TaskList.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import Task from './Task.jsx';
import Task from './Task'; // Import Task component
import './TaskList.css';

const TaskList = ({ tasks }) => {
const TaskList = ({ tasks, onToggleComplete, onDeleteTask }) => {
const getTaskListJSX = (tasks) => {
return tasks.map((task) => {
return (
Expand All @@ -11,10 +11,13 @@ const TaskList = ({ tasks }) => {
id={task.id}
title={task.title}
isComplete={task.isComplete}
onToggleComplete={onToggleComplete}
onDeleteTask={onDeleteTask}
/>
);
});
};

return <ul className="tasks__list no-bullet">{getTaskListJSX(tasks)}</ul>;
};

Expand All @@ -26,6 +29,8 @@ TaskList.propTypes = {
isComplete: PropTypes.bool.isRequired,
})
).isRequired,
onToggleComplete: PropTypes.func.isRequired,
onDeleteTask: PropTypes.func.isRequired,
};

export default TaskList;