Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving components & Todo #3

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
2,653 changes: 2,613 additions & 40 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"styled-components": "^6.0.0-rc.6"
},
"devDependencies": {
"@types/react": "^18.0.37",
Expand Down
54 changes: 37 additions & 17 deletions src/NewApp.tsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
import { useRef, useState } from "react";
import { useRef, useState, useEffect } from "react";
import { TodoService } from "./services/todo-service";
import { ITodo } from "./data-models/ITodo";
import { ITodo} from "./data-models/interfaces/ITodo";
import { TodoList } from "./components/todo-list/TodoList";
import { TodoForm } from "./components/todo-form/TodoForm";

import { ITodoListContext, TodoContextController } from "./data-models/context/todo-context";
import "./index.css";


export function NewApp() {

const [todos, setTodos] = useState<ITodo[]>([]);

const todoFormRef = useRef(null);



function removeTodo(id: string) {
const TodoListContext = TodoContextController.getContext();
const TodoListContextValue: ITodoListContext = {
removeTodo,
toggleTodo
};



function removeTodo(id: string | null) {

const canDelete = confirm("Are you sure you want to remove the todo?");
if (!canDelete)
if (!canDelete || !id)
{
return;
}


TodoService.removeTodo(id);
refreshTodo();
TodoService.removeTodo(id)
.then(() => refreshTodo());
}



function refreshTodo() {

const updatedTodos = TodoService.getTodos();
setTodos(updatedTodos);
TodoService.getTodos()
.then((updatedTodos) => {
setTodos(updatedTodos);
});
}



function addTodo(payload: ITodo): void {

TodoService.addTodo(payload);
refreshTodo();
TodoService.addTodo(payload)
.then(() => refreshTodo());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no catch block to handle promise rejection?

}


Expand All @@ -61,16 +70,27 @@ export function NewApp() {

function toggleTodo(id:string, completed:boolean) {

TodoService.toggleTodo(id, completed);
refreshTodo();
TodoService.toggleTodo(id, completed)
.then(() => refreshTodo());
}


useEffect(() => {
(async () => {
const todos = await TodoService.getTodos();
setTodos(todos);
})();
console.log("useEffect");
}, []);



return (
<div>
<div className="todo">
<TodoListContext.Provider value={TodoListContextValue}>
<TodoList todoItems={todos} />
</TodoListContext.Provider>
<TodoForm saveTodo={saveTodo} ref={todoFormRef} />
<TodoList todoItems={todos} removeTodo={removeTodo} toggleTodo={toggleTodo} />
</div>
);
}
66 changes: 59 additions & 7 deletions src/components/todo-form/TodoForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,45 @@
import { FormEvent, forwardRef, useImperativeHandle, useState } from "react";
import { ITodo } from "../../data-models/ITodo";
import { ChangeEvent, FormEvent, forwardRef, useImperativeHandle, useState } from "react";
import { ITodo } from "../../data-models/interfaces/ITodo";
import styled from 'styled-components';


interface Props {
saveTodo(todo: ITodo): void;
}


const FormInput = styled.div`
display: flex;
align-items: center;
gap: 8px;


input {
padding: 4px 8px;
height: 32px;
border: none;
border-radius: 8px;
font-size: 14px;
outline: 2px solid transparrent;
flex-grow: 1;

&:focus {
outline: 2px solid #ccc;
}
}


&.invalid input {
outline: 2px solid maroon;
}
`;


export const TodoForm = forwardRef((props: Props, ref) => {

export const TodoForm = forwardRef((props: any, ref) => {
const {saveTodo} = props;
const [title, setTitle] = useState<string>("");
const [isValid, setIsValid] = useState<boolean>(true);



Expand All @@ -18,6 +54,7 @@ export const TodoForm = forwardRef((props: any, ref) => {
e.preventDefault();
if(!title.trim())
{
setIsValid(false);
return;
}

Expand All @@ -36,14 +73,29 @@ export const TodoForm = forwardRef((props: any, ref) => {
useImperativeHandle(ref, () => ({
resetForm: resetForm
}));



function onChangeTitle(event: ChangeEvent<HTMLInputElement>) {

let val = event.target.value;
if(!!val)
{
setIsValid(true);
}


setTitle(val);
}



return (
<form onSubmit={submitForm}>
<label htmlFor="title">Title</label>
<input type="text" id="title" required value={title} onChange={(e) => setTitle(e.target.value)}></input>
<button type="submit" disabled={!title}>Add</button>
<form onSubmit={submitForm} style={{margin: "12px"}}>
<FormInput className={!isValid && 'invalid'}>
<input placeholder="Add Your Todo" type="text" id="title" required value={title} onChange={onChangeTitle}></input>
<button type="submit" disabled={!title}>Add</button>
</FormInput>
</form>
);
});
9 changes: 5 additions & 4 deletions src/components/todo-item/TodoItem.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
.todo-item {
background-color: white;
padding: 16px 8px;
border-radius: 4px;
background-color: #f0f0f0;
padding: 8px;
border-radius: 8px;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
margin: 8px;
}

.checkcontainer {
Expand Down Expand Up @@ -42,7 +43,7 @@
left: 0;
height: 21px;
width: 21px;
background-color: #eee;
background-color: white;
border-radius: 50%;
}
.checkmark:after {
Expand Down
32 changes: 28 additions & 4 deletions src/components/todo-item/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
import "./TodoItem.scss";
import { TodoContextController } from "../../data-models/context/todo-context";
import { ITodo } from "../../data-models/interfaces/ITodo";
import { useContext } from "react";


interface ITodoItemProps {
todo: ITodo;
}


export const TodoItem = (props: ITodoItemProps) => {

const { todo } = props;

const context = TodoContextController.getContext();
const todoContext = useContext(context);


function onToggleTodo(id: string, checked: boolean) {
todoContext.toggleTodo(id, checked);
}


function onRemoveTodo(id: string): void {
todoContext.removeTodo(id);
}

export const TodoItem = (props: any) => {
const { todo, removeTodo, toggleTodo } = props;

return (
<li className="todo-item">
<label className="checkcontainer" style={{ textDecoration: todo.complete ? "line-through" : "none" }}>
<input type="checkbox" checked={todo.complete} onChange={(e) => toggleTodo(todo.id, e.target.checked)}></input>
<input type="checkbox" checked={todo.complete} onChange={e => onToggleTodo(todo.id, e.target.checked)}></input>
{todo.title}
<span className="checkmark"></span>
</label>

<button onClick={() => removeTodo(todo.id)}>Remove</button>
<button onClick={_ => onRemoveTodo(todo.id)}>Remove</button>
</li>
);
};
7 changes: 5 additions & 2 deletions src/components/todo-list/TodoList.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
.todo-list {
list-style-type: none;
padding: 0;

height: calc(100% - 51px);
background: white;
margin: 0;
overflow-y: auto;

&--empty {
text-align: center;
text-align: center;
}
}
28 changes: 17 additions & 11 deletions src/components/todo-list/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { ITodo } from "../../data-models/ITodo";
import { ITodo } from "../../data-models/interfaces/ITodo";
import { TodoItem } from "../todo-item/TodoItem";
import "./TodoList.scss";

export const TodoList = (props: any) => {
const { todoItems, removeTodo, toggleTodo } = props;

return (
<ul className="todo-list">
{todoItems.length == 0 && <p className="todo-list--empty">No Todos</p>}
interface ITodoListProps {
todoItems?: ITodo[];
}

{todoItems.map((item: ITodo) => {
return <TodoItem key={item.id} todo={item} removeTodo={removeTodo} toggleTodo={toggleTodo} />;
})}
</ul>
);

export const TodoList = (props: ITodoListProps) => {
const { todoItems } = props;

const hasTodo = todoItems && todoItems.length > 0;

const noTodos = <p className="todo-list--empty">No Todos</p>;

const todos = todoItems?.map((item: ITodo) => {
return <TodoItem key={item.id} todo={item} />;
});

return <ul className="todo-list">{hasTodo ? todos : noTodos}</ul>;
};
38 changes: 38 additions & 0 deletions src/data-models/context/todo-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createContext } from "react";



export interface ITodoListContext {
Provider?: React.Provider<ITodoListContext>;
removeTodo(id: string): void;
toggleTodo(id: string, completed: boolean): void;
}



export const TodoContextController = (() => {

let context: React.Context<ITodoListContext> | undefined;



function initializeContext() {
return createContext(undefined);
}



function getContext() {

if (!context)
{
context = initializeContext();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about we directly call createContext here? can't see a reason to wrap it in another function

}
return context;
}


return {
getContext,
};
})();
File renamed without changes.
16 changes: 11 additions & 5 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ a:hover {

body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
justify-content: center;
height: 100vh;
background-color: #f0f0f0;
}

Expand Down Expand Up @@ -69,3 +65,13 @@ button:focus-visible {
background-color: #f9f9f9;
}
}


div#root {
height: 100%;
}
.todo {
display: flex;
flex-direction: column;
height: 100%;
}
Loading