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
+14
View File
@@ -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
View File
@@ -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?
+21
View File
@@ -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.
+37
View File
@@ -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
![Repobeats analytics image](https://repobeats.axiom.co/api/embed/6f0d3b6f2f8157548c3947150b58eb65af1bc462.svg)
## 📝 License
Copyright © 2023 [LucasVbr](https://github.com/LucasVbr).<br />
This project is [MIT](./LICENCE) licensed.
[//]: # (_This README was generated with ❤️ by [readme-md-generator]&#40;https://github.com/kefranabg/readme-md-generator&#41;_)
+13
View File
@@ -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>
+3400
View File
File diff suppressed because it is too large Load Diff
+35
View File
@@ -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"
}
}
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
plugins: [require('tailwindcss'), require('autoprefixer')],
};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

+5
View File
@@ -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
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" />
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
content: ['./src/**/*.{ts,tsx}'],
plugins: [require('daisyui')],
};
+25
View File
@@ -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" }]
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})