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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ Describe how you approached to problem, and what tools and techniques you used t

## View it live

Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about.
https://todolist-forest.netlify.app/
474 changes: 455 additions & 19 deletions code/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
"private": true,
"dependencies": {
"@babel/eslint-parser": "^7.18.9",
"@reduxjs/toolkit": "^1.9.5",
"eslint": "^8.21.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"styled-components": "^5.3.9",
"uniqid": "^5.4.0"
},
"scripts": {
"start": "react-scripts start",
Expand Down
Binary file added code/public/icons8-to-do.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion code/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Technigo React App</title>
<title>To do-list created in React Redux</title>
<link rel="icon" type="image/png" size="16x16" href="./icons8-to-do.png">

<!-- <a target="_blank" href="https://icons8.com/icon/4534/to-do">To Do</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a> --->
</head>

<body>
Expand Down
27 changes: 22 additions & 5 deletions code/src/App.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import React from 'react';
import { Provider } from 'react-redux';
import { combineReducers, configureStore } from '@reduxjs/toolkit';
// import styled from 'styled-components';
import Header from './components/Header';
import ToDo from './components/ToDo';
// import image from './assets/forest.jpg';

import todos from './reducers/list'

export const App = () => {
const reducer = combineReducers({
todos: todos.reducer
})

const store = configureStore({
reducer
})

return (
<div>
Find me in src/app.js!
</div>
);
}
<Provider store={store}>
<Header />
<ToDo />
</Provider>
)
};
Binary file added code/src/assets/forest.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 72 additions & 0 deletions code/src/components/AddToDo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import uniqid from 'uniqid';

import todos from 'reducers/list';

const ListContainer = styled.form`
line-height: 16px;
padding-bottom: 10px;
`;

const InputField = styled.input`
background: #fff;
color: #353a47;
border: none;
padding: 8px;
font-size: 18px;

:focus {
outline: none;
}
`;

const AddButton = styled.button`
font-size: 18px;
border: none;
background: transparent;
cursor: pointer;
margin-left: 10px;
`;

const AddPlus = styled.span`
font-size: 26px;
color: #fff;
`;

const AddTodo = () => {
const [inputValue, setInputValue] = useState('');
const dispatch = useDispatch();

const onFormSubmit = (event) => {
event.preventDefault();

const newTodo = {
id: uniqid(),
key: todos.id,
text: inputValue,
isDone: false
};
dispatch(todos.actions.addItem(newTodo));
setInputValue('');
};

return (
<ListContainer onSubmit={onFormSubmit}>
<InputField
type="text"
placeholder="Add your new task here"
value={inputValue}
required
onChange={(e) => setInputValue(e.target.value)} />
<AddButton type="submit" disabled={inputValue.length > 100}>
<AddPlus role="img" aria-label="plus sign emoji">
</AddPlus>
</AddButton>
</ListContainer>
);
};

export default AddTodo;
26 changes: 26 additions & 0 deletions code/src/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import styled from 'styled-components';

const HeaderTitle = styled.h1`
font-family: 'Quicksand', sans-serif;
text-transform: uppercase;
letter-spacing: 4px;
font-size: 30px;
color: white;
text-align: center;
background-color: rgba(237, 202, 127, 0.4);

@media (min-width: 1025px) {
font-style: italic;
font-size: 40px;
color: black;
background-color: rgba(237, 202, 127, 0.2);
}
`
const Header = () => {
return (
<HeaderTitle>Your very own to do-list</HeaderTitle>
)
}

export default Header;
97 changes: 97 additions & 0 deletions code/src/components/ToDo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* eslint-disable max-len */
/* eslint-disable jsx-a11y/label-has-associated-control */
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import styled from 'styled-components';

import todos from 'reducers/list'
import AddTodo from './AddToDo';
import TodoItem from './ToDoItem';

const TodoContainer = styled.div`
background-color: rgba(20, 20, 20, 0.5);
color: #b6c8a9;
width: 80vw;
height: auto;
align-item: center;
text-align: center;
padding: 10px;
margin: 50px auto;
margin-bottom: 40px;
border-radius: 25px;
box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);

h3 {
color: white;
font-size: 14px;
}

@media (min-width: 668px) {
width: 55vw;
}
`

const Button = styled.button`
font-size: 18px;
padding: 10px 18px;
margin-top: 15px;
background-color: #edca7f;
color: #fff;
border: none;
border-radius: 40px 0px 40px 0px;
cursor: pointer;

:hover {
color: #353a47;
}
`

const ToDo = () => {
const dispatch = useDispatch()
const todoList = useSelector((store) => store.todos.items)

const toggleTodo = (id) => {
dispatch(todos.actions.toggleItem(id))
}

const deleteTodo = (index) => {
dispatch(todos.actions.deleteItem(index))
}

const completeAll = () => {
todoList.forEach((todo) => {
if (!todo.isDone) {
dispatch(todos.actions.toggleItem(todo.id));
}
});
};

const tasksTodo = todoList.filter((todo) => !todo.isDone);
const doneTasks = todoList.filter((todo) => todo.isDone);

return (
<TodoContainer>
<h2>
You have {tasksTodo.length} {tasksTodo.length === 1 ? 'thing' : 'things'} to do today.
</h2>
<h3>(Click on a task to mark it as completed)</h3>

{tasksTodo.length === 0 && <p>You are all done!</p>}
{tasksTodo.map((todo, index) => (
<TodoItem todo={todo} index={index} key={todo.id} deleteTodo={deleteTodo} toggleTodo={toggleTodo} />
))}
<AddTodo />
<Button type="button" onClick={completeAll}>
Complete all tasks
</Button>
<h2>
You have completed {doneTasks.length} {doneTasks.length === 1 ? 'task' : 'tasks'}
</h2>
{doneTasks.map((todo, index) => (
<TodoItem todo={todo} index={index} key={todo.id} deleteTodo={deleteTodo} toggleTodo={toggleTodo} />
))}
</TodoContainer>
);
};

export default ToDo;
40 changes: 40 additions & 0 deletions code/src/components/ToDoItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import styled from 'styled-components';

const SingleTodo = styled.div`
margin: 15px;
border-bottom: 1px solid black;
padding-bottom: 15px;
line-height: 16px;
`
const TodoText = styled.div`
cursor: pointer;
text-decoration: ${(props) => (props.isDone ? 'line-through' : 'none')};
font-size: 22px;
`

const DeleteButton = styled.button`
background: #f6f6f6;
border: 1px solid #84b082;
font-size: 16px;
cursor: pointer;
border-radius: 50%;
padding: 0px 9px;
color: #84b082;
`

const TodoItem = ({ todo, index, deleteTodo, toggleTodo }) => {
return (
<SingleTodo key={todo.id}>
<TodoText onClick={() => toggleTodo(todo.id)} isDone={todo.isDone}>
<p>{todo.text}</p>
</TodoText>
<DeleteButton onClick={() => deleteTodo(index)} type="button" title="Remove task">
{' '}
X
</DeleteButton>
</SingleTodo>
);
};

export default TodoItem;
12 changes: 11 additions & 1 deletion code/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,19 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-image: url("../src/assets/forest.jpg");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
background-color: #4b7f52;
font-size: 16px;
height: 100vh;
display: flex;
justify-content: center;
text-align: center;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
}
41 changes: 41 additions & 0 deletions code/src/reducers/list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable max-len */
import { createSlice } from '@reduxjs/toolkit';

const todos = createSlice({
name: 'todos',
initialState: {
items: [
{ id: '1', text: 'Enjoy the nice weather', isDone: false },
{ id: '2', text: 'Eat some nice food', isDone: false }
]
},
// The reducers field contains functions to modify the state of the slice (todo made with createSlice)
reducers: {
toggleItem: (store, action) => {
store.items.forEach((item) => {
if (item.id === action.payload) {
item.isDone = !item.isDone;
}
});
},
deleteItem: (store, action) => {
const updatedItems = store.items.filter((item) => {
return store.items.indexOf(item) !== action.payload;
});
store.items = updatedItems;
},
addItem: (store, action) => {
store.items = [...store.items, action.payload];
},
completeAll: (store) => {
store.items.forEach((item) => {
item.isDone = true;
});
},
clearAll: (store) => {
store.items = [];
}
}
});

export default todos;