mirror of
https://github.com/LucasVbr/todo-list.git
synced 2026-05-13 17:22:04 +00:00
Rebuild the app using React ⚛️
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': 'warn',
|
||||||
|
},
|
||||||
|
}
|
||||||
+24
-2
@@ -1,2 +1,24 @@
|
|||||||
.idea/
|
# Logs
|
||||||
**/.DS_Store
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Lucàs Vabre
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<h1 align="center">Welcome to Todo List 👋</h1>
|
||||||
|
<p>
|
||||||
|
<img alt="Version" src="https://img.shields.io/badge/version-1.0.0-blue.svg?cacheSeconds=2592000" />
|
||||||
|
<a href="./LICENCE" target="_blank">
|
||||||
|
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
> A Todo List app using React, Redux-Toolkit and DaisyUI
|
||||||
|
|
||||||
|
### 🏠 [Homepage](https://todo-list-lucasvbr.vercel.app/)
|
||||||
|
|
||||||
|
<kbd>
|
||||||
|
<img src="preview.png" alt="preview picture"/>
|
||||||
|
</kbd>
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
👤 **LucasVbr**
|
||||||
|
|
||||||
|
* Github: [@LucasVbr](https://github.com/LucasVbr)
|
||||||
|
* LinkedIn: [@lucasvbr](https://linkedin.com/in/lucasvbr)
|
||||||
|
|
||||||
|
## Show your support
|
||||||
|
|
||||||
|
Give a ⭐️ if this project helped you!
|
||||||
|
|
||||||
|
## Project Stats
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 📝 License
|
||||||
|
|
||||||
|
Copyright © 2023 [LucasVbr](https://github.com/LucasVbr).<br />
|
||||||
|
This project is [MIT](./LICENCE) licensed.
|
||||||
|
|
||||||
|
[//]: # (_This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_)
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/icons/plus.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Todo List App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+3400
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "todo-list",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-redux": "^8.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.0.37",
|
||||||
|
"@types/react-dom": "^18.0.11",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||||
|
"@typescript-eslint/parser": "^5.59.0",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.0.0",
|
||||||
|
"autoprefixer": "^10.4.14",
|
||||||
|
"clsx": "^1.2.1",
|
||||||
|
"daisyui": "^3.1.0",
|
||||||
|
"eslint": "^8.38.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.3.4",
|
||||||
|
"postcss": "^8.4.24",
|
||||||
|
"tailwindcss": "^3.3.2",
|
||||||
|
"typescript": "^5.0.2",
|
||||||
|
"vite": "^4.3.9"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: [require('tailwindcss'), require('autoprefixer')],
|
||||||
|
};
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 322 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
|
||||||
|
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M12 4v16m8-8H4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 227 B |
+18
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
@@ -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>,
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
type TodoState = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
checked: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TodoState;
|
||||||
@@ -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
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
content: ['./src/**/*.{ts,tsx}'],
|
||||||
|
plugins: [require('daisyui')],
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react-swc'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user