Rebuild the app using React ⚛️

This commit is contained in:
Lucàs
2023-06-16 14:05:45 +02:00
parent e9cfbacd2c
commit 151ad7f2f9
28 changed files with 3816 additions and 2 deletions
+18
View File
@@ -0,0 +1,18 @@
import Navbar from './components/Navbar.tsx';
import Footer from './components/Footer.tsx';
import TodoCard from './components/todo/TodoCard.tsx';
export default function App() {
return (
<div className={"w-screen min-h-screen bg-blue-50"}>
<Navbar/>
<main className={"w-full min-h-screen flex justify-center items-center"}>
<TodoCard/>
</main>
<Footer/>
</div>
)
}
+9
View File
@@ -0,0 +1,9 @@
export default function Footer() {
return (
<footer className={"Footer footer w-screen fixed bottom-0 p-4"}>
<p className={"inline-block w-full text-center"}>
MIT Licence © 2023 <a className={"link"} href={"https://github.com/LucasVbr/todo-list"} target={"_blank"}>See code on GitHub</a>
</p>
</footer>
)
}
+7
View File
@@ -0,0 +1,7 @@
export default function Navbar() {
return (
<div className={"Navbar navbar fixed top-0"}>
<button className={"btn btn-ghost normal-case text-xl"}>Todo List App</button>
</div>
)
}
+33
View File
@@ -0,0 +1,33 @@
import {FormEvent, useState} from 'react';
import {useDispatch} from 'react-redux';
import {addTodo} from '../../features/todo/todoSlice.ts';
export default function TodoAddItemForm() {
const [name, setName] = useState('');
const dispatch = useDispatch();
const handleSubmit = (evt: FormEvent) => {
evt.preventDefault();
if (!name) return;
dispatch(addTodo(name));
setName("");
};
return (
<form onSubmit={handleSubmit} className={'TodoAddItemForm'}>
<div className={'form-control'}>
<div className={'input-group'}>
<input type={'text'} value={name} placeholder={'Type here your task...'}
className={'input input-bordered w-full'}
onChange={evt => setName(evt.target.value)}
/>
<button type={'submit'} className={'btn btn-square'}>
<img className={'h-6 w-6'} src={'/icons/plus.svg'}
alt="Plus icon"/>
</button>
</div>
</div>
</form>
);
}
+22
View File
@@ -0,0 +1,22 @@
import TodoItemList from './TodoItemList.tsx';
import TodoAddItemForm from './TodoAddItemForm.tsx';
import TodoCardFooter from './TodoCardFooter.tsx';
import {useSelector} from 'react-redux';
import {RootState} from '../../store.ts';
export default function TodoCard() {
const todos = useSelector((state: RootState) => state.todo);
return (
<div className={'TodoCard card bg-base-100 shadow-xl p-5 gap-7 transition ease-in-out delay-150'}>
<TodoAddItemForm/>
{todos.length > 0 && (
<>
<TodoItemList todos={todos}/>
<TodoCardFooter/>
</>
)}
</div>
);
}
+20
View File
@@ -0,0 +1,20 @@
import {useDispatch, useSelector} from 'react-redux';
import {deleteSelectedTodos} from '../../features/todo/todoSlice.ts';
import {RootState} from '../../store.ts';
import TodoState from '../../models/TodoState.ts';
export default function TodoCardFooter() {
const dispatch = useDispatch();
const selectedTodosCount: number = useSelector((state: RootState) => state.todo.filter((todo: TodoState) => !todo.checked).length);
const handleDelete = () => dispatch(deleteSelectedTodos());
return (
<div className={'flex justify-between items-center gap-5'}>
<p>You have {selectedTodosCount} pending task(s)</p>
<button onClick={handleDelete} className={'btn btn-error'}>Clear
</button>
</div>
);
}
+21
View File
@@ -0,0 +1,21 @@
import type TodoState from '../../models/TodoState.ts';
import clsx from 'clsx';
import {useDispatch} from 'react-redux';
import {checkTodo} from '../../features/todo/todoSlice.ts';
type Props = {
todo: TodoState
}
export default function TodoItem({todo}: Props) {
const dispatch = useDispatch();
const handleCheck = () => dispatch(checkTodo(todo.id));
return (
<label className={clsx('TodoItem', 'shadow rounded-lg p-4 w-full flex items-center justify-between cursor-pointer')}>
{todo.name}
<input type={"checkbox"} onChange={handleCheck} checked={todo.checked} className={"checkbox checkbox-primary"}/>
</label>
);
}
+14
View File
@@ -0,0 +1,14 @@
import TodoItem from './TodoItem.tsx';
import type TodoState from '../../models/TodoState.ts';
type Props = {
todos: TodoState[]
}
export default function TodoItemList({todos}: Props) {
return (
<div className={'TodoItemList w-full flex flex-col gap-2'}>
{todos.map((todo: TodoState, index) => <TodoItem todo={todo} key={index}/>)}
</div>
);
}
+37
View File
@@ -0,0 +1,37 @@
import TodoState from '../../models/TodoState.ts';
import {createSlice} from '@reduxjs/toolkit';
import todoState from '../../models/TodoState.ts';
const initialState: TodoState[] = [];
export const todoSlice = createSlice({
name: 'todo',
initialState,
reducers: {
addTodo: (state, action) => {
const newTodo: TodoState = {
id: Date.now(),
name: action.payload,
checked: false,
};
state.push(newTodo);
},
checkTodo: (state, action) => {
const id: number = action.payload;
const todo: TodoState | undefined = state.find(
(todo: todoState) => todo.id === id,
);
if (todo === undefined) return;
todo.checked = !todo.checked;
},
deleteSelectedTodos: (state) => {
return state.filter((todo: TodoState) => !todo.checked);
},
},
});
export const {addTodo, checkTodo, deleteSelectedTodos} = todoSlice.actions;
export default todoSlice.reducer;
+3
View File
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+14
View File
@@ -0,0 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import {Provider} from 'react-redux';
import {store} from './store.ts';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<Provider store={store}>
<App/>
</Provider>
</React.StrictMode>,
);
+7
View File
@@ -0,0 +1,7 @@
type TodoState = {
id: number
name: string
checked: boolean
}
export default TodoState;
+12
View File
@@ -0,0 +1,12 @@
import { configureStore } from '@reduxjs/toolkit'
import todoReducer from "./features/todo/todoSlice.ts"
export const store = configureStore({
reducer: {
todo: todoReducer
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />