From 12912689b7b9757abe36adbfcc853d3bdef10740 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 05:57:57 -0300 Subject: [PATCH 01/25] fix: fixes filter conditions --- package.json | 2 +- src/components/TaskManager.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 72d99a3..c1089d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.0.1", + "version": "0.0.2", "private": true, "scripts": { "dev": "vite", diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx index 7b280e8..422f77c 100644 --- a/src/components/TaskManager.tsx +++ b/src/components/TaskManager.tsx @@ -10,10 +10,10 @@ const TaskManager = () => { const [filter, setFilter] = useState("all"); const [newTask, setNewTask] = useState(); - // Intentional bug: The filter conditions are reversed. + // Fixed filter conditions const filteredTasks = tasks.filter((task) => { - if (filter === "completed") return task.completed === false; - if (filter === "pending") return task.completed === true; + if (filter === "completed") return task.completed === true; + if (filter === "pending") return task.completed === false; return true; }); From cf048ccbf135491ae6d85b0f3d4ffb8b37df864f Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 06:09:20 -0300 Subject: [PATCH 02/25] fix: fixes task deletions --- package.json | 2 +- src/components/TaskManager.tsx | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index c1089d3..badaaca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.0.2", + "version": "0.0.3", "private": true, "scripts": { "dev": "vite", diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx index 422f77c..b61cb69 100644 --- a/src/components/TaskManager.tsx +++ b/src/components/TaskManager.tsx @@ -29,13 +29,10 @@ const TaskManager = () => { setNewTask(""); }; - // Intentional bug: Directly mutating the tasks array when deleting. + // Fixed: Creating a new array for immutable state updates with .filter when deleting const handleDeleteTask = (id: number) => { - const index = tasks.findIndex((task) => task.id === id); - if (index !== -1) { - tasks.splice(index, 1); - setTasks(tasks); - } + const updatedTasks = tasks.filter(task => task.id !== id); + setTasks(updatedTasks); }; const toggleTaskCompletion = (id: number) => { From eea8ef7c0fdf7356557239bda64bbf60d7b0e6a5 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 06:33:10 -0300 Subject: [PATCH 03/25] refactor: improved overall styling and structure --- package.json | 2 +- src/components/TaskItem.tsx | 11 ++----- src/components/TaskManager.tsx | 53 ++++++++++++++++++++++------------ 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index badaaca..e415a0f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.0.3", + "version": "0.0.4", "private": true, "scripts": { "dev": "vite", diff --git a/src/components/TaskItem.tsx b/src/components/TaskItem.tsx index 6c2a176..ae96fee 100644 --- a/src/components/TaskItem.tsx +++ b/src/components/TaskItem.tsx @@ -2,11 +2,11 @@ import React from "react"; const TaskItem = ({ task, onDelete, onToggle }: any) => { return ( -
  • +
  • onToggle(task.id)} className={`cursor-pointer ${ - task.isCompleted ? "text-black" : "line-through text-green-500" + task.completed ? "line-through text-green-500" : "text-black" }`} > {task.title} @@ -14,12 +14,7 @@ const TaskItem = ({ task, onDelete, onToggle }: any) => { diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx index b61cb69..45e3b0d 100644 --- a/src/components/TaskManager.tsx +++ b/src/components/TaskManager.tsx @@ -42,43 +42,60 @@ const TaskManager = () => { }; return ( -
    -
    +
    + setNewTask(e.target.value)} - className="flex-grow border rounded-l py-2 px-3" + className="flex-grow border border-gray-300 rounded-l-md py-2 px-4 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> - -
    - -
    -
      - {filteredTasks.map((task) => ( - - ))} -
    + +
    + {filteredTasks.length > 0 ? ( +
      + {filteredTasks.map((task) => ( + + ))} +
    + ) : ( +

    No tasks found

    + )} +
    ); }; From 2659ec60cb829ce645c7711936c9acdb27569ce2 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 06:47:01 -0300 Subject: [PATCH 04/25] refactor: add TypeScript types --- package.json | 2 +- src/components/TaskItem.tsx | 28 +++++++++++++++++++--------- src/components/TaskManager.tsx | 22 +++++++++++++--------- src/components/types.ts | 13 +++++++++++++ 4 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 src/components/types.ts diff --git a/package.json b/package.json index e415a0f..952489f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.0.4", + "version": "0.0.5", "private": true, "scripts": { "dev": "vite", diff --git a/src/components/TaskItem.tsx b/src/components/TaskItem.tsx index ae96fee..e6ff8e1 100644 --- a/src/components/TaskItem.tsx +++ b/src/components/TaskItem.tsx @@ -1,16 +1,26 @@ import React from "react"; -const TaskItem = ({ task, onDelete, onToggle }: any) => { +import { TaskItemProps } from "./types"; + +const TaskItem: React.FC = ({ task, onDelete, onToggle }) => { return (
  • - onToggle(task.id)} - className={`cursor-pointer ${ - task.completed ? "line-through text-green-500" : "text-black" - }`} - > - {task.title} - +
    + onToggle(task.id)} + className="h-4 w-4 text-blue-600 rounded focus:ring-blue-500" + /> + onToggle(task.id)} + className={`cursor-pointer ${ + task.completed ? "line-through text-gray-500" : "text-gray-800" + }`} + > + {task.title} + +
    -
  • - ); -}; - -export default TaskItem; diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx deleted file mode 100644 index 5f2d263..0000000 --- a/src/components/TaskManager.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { useState } from "react"; - -import TaskItem from "./TaskItem"; -import { Task, FilterType } from "./types"; - -const TaskManager = () => { - const [tasks, setTasks] = useState([ - { id: 1, title: "Buy groceries", completed: false }, - { id: 2, title: "Clean the house", completed: true }, - ]); - const [filter, setFilter] = useState('all'); - const [newTask, setNewTask] = useState(""); - - // Fixed filter conditions - const filteredTasks = tasks.filter((task) => { - if (filter === "completed") return task.completed === true; - if (filter === "pending") return task.completed === false; - return true; - }); - - const handleAddTask = (e: React.FormEvent) => { - e.preventDefault(); - if (newTask.trim() === "") return; - - const newTaskObj: Task = { - id: tasks.length + 1, - title: newTask, - completed: false, - }; - - setTasks([...tasks, newTaskObj]); - setNewTask(""); - }; - - // Fixed: Creating a new array for immutable state updates with .filter when deleting - const handleDeleteTask = (id: number) => { - const updatedTasks = tasks.filter(task => task.id !== id); - setTasks(updatedTasks); - }; - - const toggleTaskCompletion = (id: number) => { - const updatedTasks = tasks.map((task) => - task.id === id ? { ...task, completed: !task.completed } : task - ); - setTasks(updatedTasks); - }; - - return ( -
    -
    - setNewTask(e.target.value)} - className="flex-grow border border-gray-300 rounded-l-md py-2 px-4 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> - -
    - -
    - - - -
    - -
    - {filteredTasks.length > 0 ? ( -
      - {filteredTasks.map((task) => ( - - ))} -
    - ) : ( -

    No tasks found

    - )} -
    -
    - ); -}; - -export default TaskManager; diff --git a/src/components/design-system/Button/Button.tsx b/src/components/design-system/Button/Button.tsx new file mode 100644 index 0000000..7341cd5 --- /dev/null +++ b/src/components/design-system/Button/Button.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import { ButtonProps } from "./types"; +import { baseStyles, variantStyles } from "./styles"; + +export const Button: React.FC = ({ + children, + onClick, + type = "button", + variant = "primary", + className = "", +}) => ( + +); diff --git a/src/components/design-system/Button/index.ts b/src/components/design-system/Button/index.ts new file mode 100644 index 0000000..e22c29a --- /dev/null +++ b/src/components/design-system/Button/index.ts @@ -0,0 +1 @@ +export * from "./Button"; diff --git a/src/components/design-system/Button/styles.tsx b/src/components/design-system/Button/styles.tsx new file mode 100644 index 0000000..a222615 --- /dev/null +++ b/src/components/design-system/Button/styles.tsx @@ -0,0 +1,7 @@ +export const baseStyles = "px-4 py-2 rounded-md transition-colors font-medium"; + +export const variantStyles = { + primary: "bg-blue-500 hover:bg-blue-600 text-white", + secondary: "bg-gray-100 text-gray-700 hover:bg-gray-200", + danger: "bg-red-500 hover:bg-red-600 text-white", +}; diff --git a/src/components/design-system/Button/types.tsx b/src/components/design-system/Button/types.tsx new file mode 100644 index 0000000..3dbc5c2 --- /dev/null +++ b/src/components/design-system/Button/types.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +export interface ButtonProps { + children: React.ReactNode; + onClick?: () => void; + type?: "button" | "submit" | "reset"; + variant?: "primary" | "secondary" | "danger"; + className?: string; +} diff --git a/src/components/design-system/Input/Input.tsx b/src/components/design-system/Input/Input.tsx new file mode 100644 index 0000000..8a298d3 --- /dev/null +++ b/src/components/design-system/Input/Input.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import { InputProps } from "./types"; +import { baseStyles } from "./styles"; + +export const Input: React.FC = ({ + value, + onChange, + type = "text", + placeholder = "", + className = "", +}) => ( + +); diff --git a/src/components/design-system/Input/index.ts b/src/components/design-system/Input/index.ts new file mode 100644 index 0000000..be66d76 --- /dev/null +++ b/src/components/design-system/Input/index.ts @@ -0,0 +1 @@ +export * from "./Input"; diff --git a/src/components/design-system/Input/styles.tsx b/src/components/design-system/Input/styles.tsx new file mode 100644 index 0000000..f5092a9 --- /dev/null +++ b/src/components/design-system/Input/styles.tsx @@ -0,0 +1 @@ +export const baseStyles = "border border-gray-300 py-2 px-4 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"; diff --git a/src/components/design-system/Input/types.tsx b/src/components/design-system/Input/types.tsx new file mode 100644 index 0000000..38a2bcb --- /dev/null +++ b/src/components/design-system/Input/types.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +export interface InputProps { + value: string; + onChange: (e: React.ChangeEvent) => void; + placeholder?: string; + type?: string; + className?: string; +} diff --git a/src/components/design-system/README.md b/src/components/design-system/README.md new file mode 100644 index 0000000..32ce829 --- /dev/null +++ b/src/components/design-system/README.md @@ -0,0 +1,3 @@ +# UI Design System + +Contains Atomic design components and may be later created as a separate package so it can be reused accross multiple projects. \ No newline at end of file diff --git a/src/components/design-system/index.ts b/src/components/design-system/index.ts new file mode 100644 index 0000000..9b61ffd --- /dev/null +++ b/src/components/design-system/index.ts @@ -0,0 +1,2 @@ +export * from "./Button"; +export * from "./Input"; diff --git a/src/components/task-manager/filter/TaskFilter.tsx b/src/components/task-manager/filter/TaskFilter.tsx new file mode 100644 index 0000000..08391d4 --- /dev/null +++ b/src/components/task-manager/filter/TaskFilter.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { Button } from "../../design-system"; +import { TaskFilterProps } from "./types"; + +export const TaskFilter: React.FC = ({ + currentFilter, + onFilterChange +}) => { + return ( +
    + + + +
    + ); +}; diff --git a/src/components/task-manager/filter/index.ts b/src/components/task-manager/filter/index.ts new file mode 100644 index 0000000..2144c3c --- /dev/null +++ b/src/components/task-manager/filter/index.ts @@ -0,0 +1 @@ +export * from "./TaskFilter"; diff --git a/src/components/task-manager/filter/types.tsx b/src/components/task-manager/filter/types.tsx new file mode 100644 index 0000000..341712c --- /dev/null +++ b/src/components/task-manager/filter/types.tsx @@ -0,0 +1,6 @@ +import { FilterType } from "../types"; + +export interface TaskFilterProps { + currentFilter: FilterType; + onFilterChange: (filter: FilterType) => void; +} diff --git a/src/components/task-manager/form/TaskForm.tsx b/src/components/task-manager/form/TaskForm.tsx new file mode 100644 index 0000000..7eed938 --- /dev/null +++ b/src/components/task-manager/form/TaskForm.tsx @@ -0,0 +1,32 @@ +import React, { useState, useCallback } from "react"; +import { TaskFormProps } from "./types"; +import { Button, Input } from "../../design-system"; + +export const TaskForm: React.FC = ({ onAddTask }) => { + const [newTask, setNewTask] = useState(""); + + const handleSubmit = useCallback((e: React.FormEvent) => { + e.preventDefault(); + if (newTask.trim() === "") return; + + onAddTask(newTask); + setNewTask(""); + }, [newTask, onAddTask]); + + return ( +
    + setNewTask(e.target.value)} + /> + +
    + ); +}; diff --git a/src/components/task-manager/form/index.ts b/src/components/task-manager/form/index.ts new file mode 100644 index 0000000..4949fb2 --- /dev/null +++ b/src/components/task-manager/form/index.ts @@ -0,0 +1 @@ +export * from "./TaskForm"; diff --git a/src/components/task-manager/form/types.tsx b/src/components/task-manager/form/types.tsx new file mode 100644 index 0000000..b18d595 --- /dev/null +++ b/src/components/task-manager/form/types.tsx @@ -0,0 +1,3 @@ +export interface TaskFormProps { + onAddTask: (title: string) => void; +} diff --git a/src/components/task-manager/index.ts b/src/components/task-manager/index.ts new file mode 100644 index 0000000..bf1f810 --- /dev/null +++ b/src/components/task-manager/index.ts @@ -0,0 +1 @@ +export * from "./manager/TaskManager" \ No newline at end of file diff --git a/src/components/task-manager/item/TaskItem.tsx b/src/components/task-manager/item/TaskItem.tsx new file mode 100644 index 0000000..695acd9 --- /dev/null +++ b/src/components/task-manager/item/TaskItem.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { TaskItemProps } from "./types"; +import { Button } from "../../design-system"; + +export const TaskItem: React.FC = ({ task, onDelete, onToggle }) => { + return ( +
  • +
    + onToggle(task.id)} + className={`cursor-pointer ${ + task.completed ? "line-through text-green-500" : "text-black" + }`} + > + {task.title} + +
    + + +
  • + ); +}; diff --git a/src/components/task-manager/item/types.ts b/src/components/task-manager/item/types.ts new file mode 100644 index 0000000..ef3659a --- /dev/null +++ b/src/components/task-manager/item/types.ts @@ -0,0 +1,7 @@ +import { Task } from "../types"; + +export interface TaskItemProps { + task: Task; + onDelete: (id: number) => void; + onToggle: (id: number) => void; +} diff --git a/src/components/task-manager/list/TasksList.tsx b/src/components/task-manager/list/TasksList.tsx new file mode 100644 index 0000000..369c138 --- /dev/null +++ b/src/components/task-manager/list/TasksList.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { TaskItem } from "../item/TaskItem"; +import { TasksListProps } from "./types"; + +export const TasksList: React.FC = ({ + tasks, + onDelete, + onToggle +}) => ( +
    + {tasks.length > 0 ? ( +
      + {tasks.map((task) => ( + + ))} +
    + ) : ( +

    No tasks found

    + )} +
    +); + + diff --git a/src/components/task-manager/list/index.ts b/src/components/task-manager/list/index.ts new file mode 100644 index 0000000..b4f3d9d --- /dev/null +++ b/src/components/task-manager/list/index.ts @@ -0,0 +1 @@ +export * from "./TasksList"; diff --git a/src/components/task-manager/list/types.ts b/src/components/task-manager/list/types.ts new file mode 100644 index 0000000..1607506 --- /dev/null +++ b/src/components/task-manager/list/types.ts @@ -0,0 +1,7 @@ +import { Task } from "../types"; + +export interface TasksListProps { + tasks: Task[]; + onDelete: (id: number) => void; + onToggle: (id: number) => void; +} diff --git a/src/components/task-manager/manager/TaskManager.tsx b/src/components/task-manager/manager/TaskManager.tsx new file mode 100644 index 0000000..0d84e3c --- /dev/null +++ b/src/components/task-manager/manager/TaskManager.tsx @@ -0,0 +1,63 @@ +import React, { useState, useCallback } from "react"; + +import { TaskForm } from "../form"; +import { TaskFilter } from "../filter"; +import { TasksList } from "../list"; +import { FilterType, Task } from "../types"; + +import { TaskManagerProps } from "./types"; +import { getFilteredTasks, generateTaskId } from "./helpers"; + +export const TaskManager: React.FC = ({ initialTasks = [] }) => { + const [tasks, setTasks] = useState( + initialTasks.length > 0 ? initialTasks : [ + { id: 1, title: "Buy groceries", completed: false }, + { id: 2, title: "Clean the house", completed: true }, + ] + ); + const [filter, setFilter] = useState('all'); + const filteredTasks = React.useMemo(() => getFilteredTasks(tasks, filter), [tasks, filter]); + + const handleAddTask = useCallback((title: string) => { + const newTaskObj: Task = { + id: generateTaskId(tasks), + title, + completed: false, + }; + + setTasks(prevTasks => [...prevTasks, newTaskObj]); + }, [tasks]); + + const handleDeleteTask = useCallback((id: number) => { + setTasks(prevTasks => prevTasks.filter(task => task.id !== id)); + }, []); + + const toggleTaskCompletion = useCallback((id: number) => { + setTasks(prevTasks => + prevTasks.map(task => + task.id === id ? { ...task, completed: !task.completed } : task + ) + ); + }, []); + + const handleFilterChange = useCallback((newFilter: FilterType) => { + setFilter(newFilter); + }, []); + + return ( +
    + + + + + +
    + ); +}; diff --git a/src/components/task-manager/manager/helpers.tsx b/src/components/task-manager/manager/helpers.tsx new file mode 100644 index 0000000..053827f --- /dev/null +++ b/src/components/task-manager/manager/helpers.tsx @@ -0,0 +1,15 @@ +import { FilterType } from "../filter/types"; +import { Task } from "../types"; + +export const getFilteredTasks = (tasks: Task[], filter: FilterType): Task[] => { + return tasks.filter((task) => { + if (filter === "completed") return task.completed === true; + if (filter === "pending") return task.completed === false; + return true; + }); +}; +export const generateTaskId = (tasks: Task[]): number => { + return tasks.length > 0 + ? Math.max(...tasks.map(task => task.id)) + 1 + : 1; +}; diff --git a/src/components/task-manager/manager/types.ts b/src/components/task-manager/manager/types.ts new file mode 100644 index 0000000..eca894d --- /dev/null +++ b/src/components/task-manager/manager/types.ts @@ -0,0 +1,5 @@ +import { Task } from "../types"; + +export interface TaskManagerProps { + initialTasks?: Task[]; +} diff --git a/src/components/types.ts b/src/components/task-manager/types.ts similarity index 53% rename from src/components/types.ts rename to src/components/task-manager/types.ts index eff46df..340db72 100644 --- a/src/components/types.ts +++ b/src/components/task-manager/types.ts @@ -5,9 +5,3 @@ export interface Task { } export type FilterType = 'all' | 'completed' | 'pending'; - -export interface TaskItemProps { - task: Task; - onDelete: (id: number) => void; - onToggle: (id: number) => void; -} diff --git a/src/main.tsx b/src/main.tsx index 6490d9c..17a9e4e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client"; import "./index.css"; -import App from "./App"; +import { App } from "./App"; ReactDOM.createRoot(document.getElementById("root")!).render( From 96c86ebe960d0f2d22bd66b9874c781e6901b212 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 08:33:21 -0300 Subject: [PATCH 06/25] refactor: implements basic tasks state management with zustand --- package.json | 13 ++-- pnpm-lock.yaml | 26 ++++++++ .../{ => components}/filter/TaskFilter.tsx | 2 +- .../{ => components}/filter/index.ts | 0 .../{ => components}/filter/types.tsx | 2 +- .../{ => components}/form/TaskForm.tsx | 2 +- .../{ => components}/form/index.ts | 0 .../{ => components}/form/types.tsx | 0 .../task-manager/components/index.ts | 5 ++ .../{ => components}/item/TaskItem.tsx | 2 +- .../{ => components}/item/types.ts | 2 +- .../{ => components}/list/TasksList.tsx | 0 .../{ => components}/list/index.ts | 1 + .../{ => components}/list/types.ts | 2 +- .../components/manager/TaskManager.tsx | 33 ++++++++++ .../{ => components}/manager/types.ts | 4 +- src/components/task-manager/index.ts | 2 +- .../task-manager/manager/TaskManager.tsx | 63 ------------------- .../{manager => store}/helpers.tsx | 4 +- src/components/task-manager/store/index.ts | 2 + .../{types.ts => store/models.ts} | 0 .../task-manager/store/taskStore.ts | 61 ++++++++++++++++++ src/components/task-manager/store/types.ts | 11 ++++ 23 files changed, 157 insertions(+), 80 deletions(-) rename src/components/task-manager/{ => components}/filter/TaskFilter.tsx (94%) rename src/components/task-manager/{ => components}/filter/index.ts (100%) rename src/components/task-manager/{ => components}/filter/types.tsx (70%) rename src/components/task-manager/{ => components}/form/TaskForm.tsx (93%) rename src/components/task-manager/{ => components}/form/index.ts (100%) rename src/components/task-manager/{ => components}/form/types.tsx (100%) create mode 100644 src/components/task-manager/components/index.ts rename src/components/task-manager/{ => components}/item/TaskItem.tsx (93%) rename src/components/task-manager/{ => components}/item/types.ts (73%) rename src/components/task-manager/{ => components}/list/TasksList.tsx (100%) rename src/components/task-manager/{ => components}/list/index.ts (96%) rename src/components/task-manager/{ => components}/list/types.ts (73%) create mode 100644 src/components/task-manager/components/manager/TaskManager.tsx rename src/components/task-manager/{ => components}/manager/types.ts (58%) delete mode 100644 src/components/task-manager/manager/TaskManager.tsx rename src/components/task-manager/{manager => store}/helpers.tsx (84%) create mode 100644 src/components/task-manager/store/index.ts rename src/components/task-manager/{types.ts => store/models.ts} (100%) create mode 100644 src/components/task-manager/store/taskStore.ts create mode 100644 src/components/task-manager/store/types.ts diff --git a/package.json b/package.json index 42cc3a0..5135941 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.0.6", + "version": "0.0.7", "private": true, "scripts": { "dev": "vite", @@ -9,16 +9,17 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "zustand": "^5.0.3" }, "devDependencies": { "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", - "typescript": "^4.6.0", - "vite": "^4.0.0", "@vitejs/plugin-react": "^3.0.0", - "tailwindcss": "^3.0.0", + "autoprefixer": "^10.0.0", "postcss": "^8.0.0", - "autoprefixer": "^10.0.0" + "tailwindcss": "^3.0.0", + "typescript": "^4.6.0", + "vite": "^4.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fe8063..f67acf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) + zustand: + specifier: ^5.0.3 + version: 5.0.3(@types/react@18.3.18)(react@18.3.1) devDependencies: '@types/react': specifier: ^18.0.0 @@ -845,6 +848,24 @@ packages: engines: {node: '>= 14'} hasBin: true + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + snapshots: '@alloc/quick-lru@5.2.0': {} @@ -1578,3 +1599,8 @@ snapshots: yallist@3.1.1: {} yaml@2.7.0: {} + + zustand@5.0.3(@types/react@18.3.18)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.18 + react: 18.3.1 diff --git a/src/components/task-manager/filter/TaskFilter.tsx b/src/components/task-manager/components/filter/TaskFilter.tsx similarity index 94% rename from src/components/task-manager/filter/TaskFilter.tsx rename to src/components/task-manager/components/filter/TaskFilter.tsx index 08391d4..9c507b2 100644 --- a/src/components/task-manager/filter/TaskFilter.tsx +++ b/src/components/task-manager/components/filter/TaskFilter.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Button } from "../../design-system"; +import { Button } from "../../../design-system"; import { TaskFilterProps } from "./types"; export const TaskFilter: React.FC = ({ diff --git a/src/components/task-manager/filter/index.ts b/src/components/task-manager/components/filter/index.ts similarity index 100% rename from src/components/task-manager/filter/index.ts rename to src/components/task-manager/components/filter/index.ts diff --git a/src/components/task-manager/filter/types.tsx b/src/components/task-manager/components/filter/types.tsx similarity index 70% rename from src/components/task-manager/filter/types.tsx rename to src/components/task-manager/components/filter/types.tsx index 341712c..97a3c28 100644 --- a/src/components/task-manager/filter/types.tsx +++ b/src/components/task-manager/components/filter/types.tsx @@ -1,4 +1,4 @@ -import { FilterType } from "../types"; +import { FilterType } from "../../store/models"; export interface TaskFilterProps { currentFilter: FilterType; diff --git a/src/components/task-manager/form/TaskForm.tsx b/src/components/task-manager/components/form/TaskForm.tsx similarity index 93% rename from src/components/task-manager/form/TaskForm.tsx rename to src/components/task-manager/components/form/TaskForm.tsx index 7eed938..0c50c96 100644 --- a/src/components/task-manager/form/TaskForm.tsx +++ b/src/components/task-manager/components/form/TaskForm.tsx @@ -1,6 +1,6 @@ import React, { useState, useCallback } from "react"; import { TaskFormProps } from "./types"; -import { Button, Input } from "../../design-system"; +import { Button, Input } from "../../../design-system"; export const TaskForm: React.FC = ({ onAddTask }) => { const [newTask, setNewTask] = useState(""); diff --git a/src/components/task-manager/form/index.ts b/src/components/task-manager/components/form/index.ts similarity index 100% rename from src/components/task-manager/form/index.ts rename to src/components/task-manager/components/form/index.ts diff --git a/src/components/task-manager/form/types.tsx b/src/components/task-manager/components/form/types.tsx similarity index 100% rename from src/components/task-manager/form/types.tsx rename to src/components/task-manager/components/form/types.tsx diff --git a/src/components/task-manager/components/index.ts b/src/components/task-manager/components/index.ts new file mode 100644 index 0000000..21f55f2 --- /dev/null +++ b/src/components/task-manager/components/index.ts @@ -0,0 +1,5 @@ +export * from "./form/TaskForm"; +export * from "./filter/TaskFilter"; +export * from "./item/TaskItem"; +export * from "./list/TasksList"; +export * from "./manager/TaskManager"; diff --git a/src/components/task-manager/item/TaskItem.tsx b/src/components/task-manager/components/item/TaskItem.tsx similarity index 93% rename from src/components/task-manager/item/TaskItem.tsx rename to src/components/task-manager/components/item/TaskItem.tsx index 695acd9..8ee47de 100644 --- a/src/components/task-manager/item/TaskItem.tsx +++ b/src/components/task-manager/components/item/TaskItem.tsx @@ -1,6 +1,6 @@ import React from "react"; import { TaskItemProps } from "./types"; -import { Button } from "../../design-system"; +import { Button } from "../../../design-system"; export const TaskItem: React.FC = ({ task, onDelete, onToggle }) => { return ( diff --git a/src/components/task-manager/item/types.ts b/src/components/task-manager/components/item/types.ts similarity index 73% rename from src/components/task-manager/item/types.ts rename to src/components/task-manager/components/item/types.ts index ef3659a..ccd44ee 100644 --- a/src/components/task-manager/item/types.ts +++ b/src/components/task-manager/components/item/types.ts @@ -1,4 +1,4 @@ -import { Task } from "../types"; +import { Task } from "../../store/models"; export interface TaskItemProps { task: Task; diff --git a/src/components/task-manager/list/TasksList.tsx b/src/components/task-manager/components/list/TasksList.tsx similarity index 100% rename from src/components/task-manager/list/TasksList.tsx rename to src/components/task-manager/components/list/TasksList.tsx diff --git a/src/components/task-manager/list/index.ts b/src/components/task-manager/components/list/index.ts similarity index 96% rename from src/components/task-manager/list/index.ts rename to src/components/task-manager/components/list/index.ts index b4f3d9d..f13a2b0 100644 --- a/src/components/task-manager/list/index.ts +++ b/src/components/task-manager/components/list/index.ts @@ -1 +1,2 @@ export * from "./TasksList"; + diff --git a/src/components/task-manager/list/types.ts b/src/components/task-manager/components/list/types.ts similarity index 73% rename from src/components/task-manager/list/types.ts rename to src/components/task-manager/components/list/types.ts index 1607506..300922b 100644 --- a/src/components/task-manager/list/types.ts +++ b/src/components/task-manager/components/list/types.ts @@ -1,4 +1,4 @@ -import { Task } from "../types"; +import { Task } from "../../store/models"; export interface TasksListProps { tasks: Task[]; diff --git a/src/components/task-manager/components/manager/TaskManager.tsx b/src/components/task-manager/components/manager/TaskManager.tsx new file mode 100644 index 0000000..49b19f7 --- /dev/null +++ b/src/components/task-manager/components/manager/TaskManager.tsx @@ -0,0 +1,33 @@ +import React from "react"; + +import { useTaskStore } from "../../store"; +import { TaskManagerProps } from "./types"; +import { TaskFilter, TaskForm, TasksList } from ".."; + +export const TaskManager: React.FC = ({ initialTasks = [] }) => { + const { + filteredTasks, + filter, + addTask, + deleteTask, + toggleTask, + setFilter + } = useTaskStore(); + + return ( +
    + + + + + +
    + ); +}; diff --git a/src/components/task-manager/manager/types.ts b/src/components/task-manager/components/manager/types.ts similarity index 58% rename from src/components/task-manager/manager/types.ts rename to src/components/task-manager/components/manager/types.ts index eca894d..c6f12a0 100644 --- a/src/components/task-manager/manager/types.ts +++ b/src/components/task-manager/components/manager/types.ts @@ -1,5 +1,5 @@ -import { Task } from "../types"; +import { Task } from "../../store/models"; export interface TaskManagerProps { initialTasks?: Task[]; -} +} \ No newline at end of file diff --git a/src/components/task-manager/index.ts b/src/components/task-manager/index.ts index bf1f810..cd59e0d 100644 --- a/src/components/task-manager/index.ts +++ b/src/components/task-manager/index.ts @@ -1 +1 @@ -export * from "./manager/TaskManager" \ No newline at end of file +export * from "./components/manager/TaskManager"; diff --git a/src/components/task-manager/manager/TaskManager.tsx b/src/components/task-manager/manager/TaskManager.tsx deleted file mode 100644 index 0d84e3c..0000000 --- a/src/components/task-manager/manager/TaskManager.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useState, useCallback } from "react"; - -import { TaskForm } from "../form"; -import { TaskFilter } from "../filter"; -import { TasksList } from "../list"; -import { FilterType, Task } from "../types"; - -import { TaskManagerProps } from "./types"; -import { getFilteredTasks, generateTaskId } from "./helpers"; - -export const TaskManager: React.FC = ({ initialTasks = [] }) => { - const [tasks, setTasks] = useState( - initialTasks.length > 0 ? initialTasks : [ - { id: 1, title: "Buy groceries", completed: false }, - { id: 2, title: "Clean the house", completed: true }, - ] - ); - const [filter, setFilter] = useState('all'); - const filteredTasks = React.useMemo(() => getFilteredTasks(tasks, filter), [tasks, filter]); - - const handleAddTask = useCallback((title: string) => { - const newTaskObj: Task = { - id: generateTaskId(tasks), - title, - completed: false, - }; - - setTasks(prevTasks => [...prevTasks, newTaskObj]); - }, [tasks]); - - const handleDeleteTask = useCallback((id: number) => { - setTasks(prevTasks => prevTasks.filter(task => task.id !== id)); - }, []); - - const toggleTaskCompletion = useCallback((id: number) => { - setTasks(prevTasks => - prevTasks.map(task => - task.id === id ? { ...task, completed: !task.completed } : task - ) - ); - }, []); - - const handleFilterChange = useCallback((newFilter: FilterType) => { - setFilter(newFilter); - }, []); - - return ( -
    - - - - - -
    - ); -}; diff --git a/src/components/task-manager/manager/helpers.tsx b/src/components/task-manager/store/helpers.tsx similarity index 84% rename from src/components/task-manager/manager/helpers.tsx rename to src/components/task-manager/store/helpers.tsx index 053827f..b400343 100644 --- a/src/components/task-manager/manager/helpers.tsx +++ b/src/components/task-manager/store/helpers.tsx @@ -1,5 +1,4 @@ -import { FilterType } from "../filter/types"; -import { Task } from "../types"; +import { Task, FilterType } from "./models"; export const getFilteredTasks = (tasks: Task[], filter: FilterType): Task[] => { return tasks.filter((task) => { @@ -8,6 +7,7 @@ export const getFilteredTasks = (tasks: Task[], filter: FilterType): Task[] => { return true; }); }; + export const generateTaskId = (tasks: Task[]): number => { return tasks.length > 0 ? Math.max(...tasks.map(task => task.id)) + 1 diff --git a/src/components/task-manager/store/index.ts b/src/components/task-manager/store/index.ts new file mode 100644 index 0000000..4086a6d --- /dev/null +++ b/src/components/task-manager/store/index.ts @@ -0,0 +1,2 @@ +export * from './models'; +export { useTasksStore as useTaskStore } from './taskStore'; diff --git a/src/components/task-manager/types.ts b/src/components/task-manager/store/models.ts similarity index 100% rename from src/components/task-manager/types.ts rename to src/components/task-manager/store/models.ts diff --git a/src/components/task-manager/store/taskStore.ts b/src/components/task-manager/store/taskStore.ts new file mode 100644 index 0000000..36bfa34 --- /dev/null +++ b/src/components/task-manager/store/taskStore.ts @@ -0,0 +1,61 @@ +import { create } from 'zustand'; +import { TaskState } from './types'; +import { FilterType, Task } from './models'; +import { generateTaskId, getFilteredTasks } from './helpers'; + +const initialTasks: Task[] = [ + { id: 1, title: "Buy groceries", completed: false }, + { id: 2, title: "Clean the house", completed: true }, +]; + +export const useTasksStore = create((set, get) => ({ + tasks: initialTasks, + filter: 'all', + filteredTasks: initialTasks, + + addTask: (title: string) => { + const { tasks } = get(); + const newTask: Task = { + id: generateTaskId(tasks), + title, + completed: false, + }; + + set(state => { + const updatedTasks = [...state.tasks, newTask]; + return { + tasks: updatedTasks, + filteredTasks: getFilteredTasks(updatedTasks, state.filter) + }; + }); + }, + + deleteTask: (id: number) => { + set(state => { + const updatedTasks = state.tasks.filter(task => task.id !== id); + return { + tasks: updatedTasks, + filteredTasks: getFilteredTasks(updatedTasks, state.filter) + }; + }); + }, + + toggleTask: (id: number) => { + set(state => { + const updatedTasks = state.tasks.map(task => + task.id === id ? { ...task, completed: !task.completed } : task + ); + return { + tasks: updatedTasks, + filteredTasks: getFilteredTasks(updatedTasks, state.filter) + }; + }); + }, + + setFilter: (filter: FilterType) => { + set(state => ({ + filter, + filteredTasks: getFilteredTasks(state.tasks, filter) + })); + } +})); diff --git a/src/components/task-manager/store/types.ts b/src/components/task-manager/store/types.ts new file mode 100644 index 0000000..7f4f4d6 --- /dev/null +++ b/src/components/task-manager/store/types.ts @@ -0,0 +1,11 @@ +import { Task, FilterType } from './models'; + +export interface TaskState { + tasks: Task[]; + filter: FilterType; + filteredTasks: Task[]; + addTask: (title: string) => void; + deleteTask: (id: number) => void; + toggleTask: (id: number) => void; + setFilter: (filter: FilterType) => void; +} From c3b28c9f74c53f3791d00704e1054acae73dea1f Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 08:37:47 -0300 Subject: [PATCH 07/25] refactor: update imports to use 'import type' for TypeScript types --- package.json | 2 +- src/components/task-manager/components/filter/TaskFilter.tsx | 2 +- src/components/task-manager/components/filter/types.tsx | 2 +- src/components/task-manager/components/form/TaskForm.tsx | 2 +- src/components/task-manager/components/item/TaskItem.tsx | 2 +- src/components/task-manager/components/item/types.ts | 2 +- src/components/task-manager/components/list/TasksList.tsx | 2 +- src/components/task-manager/components/list/types.ts | 2 +- .../task-manager/components/manager/TaskManager.tsx | 3 +-- src/components/task-manager/components/manager/types.ts | 5 ----- src/components/task-manager/store/taskStore.ts | 4 ++-- src/components/task-manager/store/types.ts | 2 +- 12 files changed, 12 insertions(+), 18 deletions(-) delete mode 100644 src/components/task-manager/components/manager/types.ts diff --git a/package.json b/package.json index 5135941..73417da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.0.7", + "version": "0.0.8", "private": true, "scripts": { "dev": "vite", diff --git a/src/components/task-manager/components/filter/TaskFilter.tsx b/src/components/task-manager/components/filter/TaskFilter.tsx index 9c507b2..1e20c5f 100644 --- a/src/components/task-manager/components/filter/TaskFilter.tsx +++ b/src/components/task-manager/components/filter/TaskFilter.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Button } from "../../../design-system"; -import { TaskFilterProps } from "./types"; +import type { TaskFilterProps } from "./types"; export const TaskFilter: React.FC = ({ currentFilter, diff --git a/src/components/task-manager/components/filter/types.tsx b/src/components/task-manager/components/filter/types.tsx index 97a3c28..f7fd596 100644 --- a/src/components/task-manager/components/filter/types.tsx +++ b/src/components/task-manager/components/filter/types.tsx @@ -1,4 +1,4 @@ -import { FilterType } from "../../store/models"; +import type { FilterType } from "../../store/models"; export interface TaskFilterProps { currentFilter: FilterType; diff --git a/src/components/task-manager/components/form/TaskForm.tsx b/src/components/task-manager/components/form/TaskForm.tsx index 0c50c96..18c4b52 100644 --- a/src/components/task-manager/components/form/TaskForm.tsx +++ b/src/components/task-manager/components/form/TaskForm.tsx @@ -1,5 +1,5 @@ import React, { useState, useCallback } from "react"; -import { TaskFormProps } from "./types"; +import type { TaskFormProps } from "./types"; import { Button, Input } from "../../../design-system"; export const TaskForm: React.FC = ({ onAddTask }) => { diff --git a/src/components/task-manager/components/item/TaskItem.tsx b/src/components/task-manager/components/item/TaskItem.tsx index 8ee47de..fe30f0c 100644 --- a/src/components/task-manager/components/item/TaskItem.tsx +++ b/src/components/task-manager/components/item/TaskItem.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { TaskItemProps } from "./types"; +import type { TaskItemProps } from "./types"; import { Button } from "../../../design-system"; export const TaskItem: React.FC = ({ task, onDelete, onToggle }) => { diff --git a/src/components/task-manager/components/item/types.ts b/src/components/task-manager/components/item/types.ts index ccd44ee..d4db569 100644 --- a/src/components/task-manager/components/item/types.ts +++ b/src/components/task-manager/components/item/types.ts @@ -1,4 +1,4 @@ -import { Task } from "../../store/models"; +import type { Task } from "../../store/models"; export interface TaskItemProps { task: Task; diff --git a/src/components/task-manager/components/list/TasksList.tsx b/src/components/task-manager/components/list/TasksList.tsx index 369c138..6a09072 100644 --- a/src/components/task-manager/components/list/TasksList.tsx +++ b/src/components/task-manager/components/list/TasksList.tsx @@ -1,6 +1,6 @@ import React from "react"; import { TaskItem } from "../item/TaskItem"; -import { TasksListProps } from "./types"; +import type { TasksListProps } from "./types"; export const TasksList: React.FC = ({ tasks, diff --git a/src/components/task-manager/components/list/types.ts b/src/components/task-manager/components/list/types.ts index 300922b..9a918e0 100644 --- a/src/components/task-manager/components/list/types.ts +++ b/src/components/task-manager/components/list/types.ts @@ -1,4 +1,4 @@ -import { Task } from "../../store/models"; +import type { Task } from "../../store/models"; export interface TasksListProps { tasks: Task[]; diff --git a/src/components/task-manager/components/manager/TaskManager.tsx b/src/components/task-manager/components/manager/TaskManager.tsx index 49b19f7..5fb69f5 100644 --- a/src/components/task-manager/components/manager/TaskManager.tsx +++ b/src/components/task-manager/components/manager/TaskManager.tsx @@ -1,10 +1,9 @@ import React from "react"; import { useTaskStore } from "../../store"; -import { TaskManagerProps } from "./types"; import { TaskFilter, TaskForm, TasksList } from ".."; -export const TaskManager: React.FC = ({ initialTasks = [] }) => { +export const TaskManager: React.FC = () => { const { filteredTasks, filter, diff --git a/src/components/task-manager/components/manager/types.ts b/src/components/task-manager/components/manager/types.ts deleted file mode 100644 index c6f12a0..0000000 --- a/src/components/task-manager/components/manager/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Task } from "../../store/models"; - -export interface TaskManagerProps { - initialTasks?: Task[]; -} \ No newline at end of file diff --git a/src/components/task-manager/store/taskStore.ts b/src/components/task-manager/store/taskStore.ts index 36bfa34..4598c54 100644 --- a/src/components/task-manager/store/taskStore.ts +++ b/src/components/task-manager/store/taskStore.ts @@ -1,6 +1,6 @@ import { create } from 'zustand'; -import { TaskState } from './types'; -import { FilterType, Task } from './models'; +import type { TaskState } from './types'; +import type { FilterType, Task } from './models'; import { generateTaskId, getFilteredTasks } from './helpers'; const initialTasks: Task[] = [ diff --git a/src/components/task-manager/store/types.ts b/src/components/task-manager/store/types.ts index 7f4f4d6..b8e3fb1 100644 --- a/src/components/task-manager/store/types.ts +++ b/src/components/task-manager/store/types.ts @@ -1,4 +1,4 @@ -import { Task, FilterType } from './models'; +import type { Task, FilterType } from './models'; export interface TaskState { tasks: Task[]; From 5d93d6dde9dd99672c63303c1f260a69cca771e6 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 19:03:57 -0300 Subject: [PATCH 08/25] feat: adds database persistence --- .gitignore | 3 +- package.json | 1 + pnpm-lock.yaml | 134 +++++++++++++++++- .../LoadingIndicator/LoadingIndicator.tsx | 7 + .../design-system/LoadingIndicator/index.ts | 1 + src/components/design-system/index.ts | 1 + .../task-manager/components/list/types.ts | 4 +- .../components/manager/TaskManager.tsx | 31 ++-- src/components/task-manager/store/helpers.tsx | 6 - src/components/task-manager/store/index.ts | 2 +- src/components/task-manager/store/models.ts | 2 +- src/components/task-manager/store/service.ts | 61 ++++++++ .../task-manager/store/taskStore.ts | 99 ++++++++++--- src/components/task-manager/store/types.ts | 8 +- src/lib/supabase.ts | 6 + 15 files changed, 321 insertions(+), 45 deletions(-) create mode 100644 src/components/design-system/LoadingIndicator/LoadingIndicator.tsx create mode 100644 src/components/design-system/LoadingIndicator/index.ts create mode 100644 src/components/task-manager/store/service.ts create mode 100644 src/lib/supabase.ts diff --git a/.gitignore b/.gitignore index 76add87..a0d218e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -dist \ No newline at end of file +dist +.env \ No newline at end of file diff --git a/package.json b/package.json index 73417da..73d8278 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "preview": "vite preview" }, "dependencies": { + "@supabase/supabase-js": "^2.49.4", "react": "^18.2.0", "react-dom": "^18.2.0", "zustand": "^5.0.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f67acf9..c042d0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@supabase/supabase-js': + specifier: ^2.49.4 + version: 2.49.4 react: specifier: ^18.2.0 version: 18.3.1 @@ -26,7 +29,7 @@ importers: version: 18.3.5(@types/react@18.3.18) '@vitejs/plugin-react': specifier: ^3.0.0 - version: 3.1.0(vite@4.5.9) + version: 3.1.0(vite@4.5.9(@types/node@22.14.0)) autoprefixer: specifier: ^10.0.0 version: 10.4.20(postcss@8.5.3) @@ -41,7 +44,7 @@ importers: version: 4.9.5 vite: specifier: ^4.0.0 - version: 4.5.9 + version: 4.5.9(@types/node@22.14.0) packages: @@ -302,6 +305,34 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@supabase/auth-js@2.69.1': + resolution: {integrity: sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==} + + '@supabase/functions-js@2.4.4': + resolution: {integrity: sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==} + + '@supabase/node-fetch@2.6.15': + resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==} + engines: {node: 4.x || >=6.0.0} + + '@supabase/postgrest-js@1.19.4': + resolution: {integrity: sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==} + + '@supabase/realtime-js@2.11.2': + resolution: {integrity: sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==} + + '@supabase/storage-js@2.7.1': + resolution: {integrity: sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==} + + '@supabase/supabase-js@2.49.4': + resolution: {integrity: sha512-jUF0uRUmS8BKt37t01qaZ88H9yV1mbGYnqLeuFWLcdV+x1P4fl0yP9DGtaEhFPZcwSom7u16GkLEH9QJZOqOkw==} + + '@types/node@22.14.0': + resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} + + '@types/phoenix@1.6.6': + resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} + '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} @@ -313,6 +344,9 @@ packages: '@types/react@18.3.18': resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@vitejs/plugin-react@3.1.0': resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} @@ -782,6 +816,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -790,6 +827,9 @@ packages: engines: {node: '>=4.2.0'} hasBin: true + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + update-browserslist-db@1.1.2: resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==} hasBin: true @@ -827,6 +867,12 @@ packages: terser: optional: true + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -840,6 +886,18 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + ws@8.18.1: + resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -1092,6 +1150,54 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@supabase/auth-js@2.69.1': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/functions-js@2.4.4': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/node-fetch@2.6.15': + dependencies: + whatwg-url: 5.0.0 + + '@supabase/postgrest-js@1.19.4': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/realtime-js@2.11.2': + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.6 + '@types/ws': 8.18.1 + ws: 8.18.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.7.1': + dependencies: + '@supabase/node-fetch': 2.6.15 + + '@supabase/supabase-js@2.49.4': + dependencies: + '@supabase/auth-js': 2.69.1 + '@supabase/functions-js': 2.4.4 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.19.4 + '@supabase/realtime-js': 2.11.2 + '@supabase/storage-js': 2.7.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@types/node@22.14.0': + dependencies: + undici-types: 6.21.0 + + '@types/phoenix@1.6.6': {} + '@types/prop-types@15.7.14': {} '@types/react-dom@18.3.5(@types/react@18.3.18)': @@ -1103,14 +1209,18 @@ snapshots: '@types/prop-types': 15.7.14 csstype: 3.1.3 - '@vitejs/plugin-react@3.1.0(vite@4.5.9)': + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.14.0 + + '@vitejs/plugin-react@3.1.0(vite@4.5.9(@types/node@22.14.0))': dependencies: '@babel/core': 7.26.9 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.9) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) magic-string: 0.27.0 react-refresh: 0.14.2 - vite: 4.5.9 + vite: 4.5.9(@types/node@22.14.0) transitivePeerDependencies: - supports-color @@ -1560,10 +1670,14 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + ts-interface-checker@0.1.13: {} typescript@4.9.5: {} + undici-types@6.21.0: {} + update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: browserslist: 4.24.4 @@ -1572,14 +1686,22 @@ snapshots: util-deprecate@1.0.2: {} - vite@4.5.9: + vite@4.5.9(@types/node@22.14.0): dependencies: esbuild: 0.18.20 postcss: 8.5.3 rollup: 3.29.5 optionalDependencies: + '@types/node': 22.14.0 fsevents: 2.3.3 + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -1596,6 +1718,8 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + ws@8.18.1: {} + yallist@3.1.1: {} yaml@2.7.0: {} diff --git a/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx b/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx new file mode 100644 index 0000000..ebcef40 --- /dev/null +++ b/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +export const LoadingIndicator = () => ( +
    +
    +
    +); diff --git a/src/components/design-system/LoadingIndicator/index.ts b/src/components/design-system/LoadingIndicator/index.ts new file mode 100644 index 0000000..266c417 --- /dev/null +++ b/src/components/design-system/LoadingIndicator/index.ts @@ -0,0 +1 @@ +export * from './LoadingIndicator'; diff --git a/src/components/design-system/index.ts b/src/components/design-system/index.ts index 9b61ffd..0852388 100644 --- a/src/components/design-system/index.ts +++ b/src/components/design-system/index.ts @@ -1,2 +1,3 @@ export * from "./Button"; export * from "./Input"; +export * from "./LoadingIndicator"; diff --git a/src/components/task-manager/components/list/types.ts b/src/components/task-manager/components/list/types.ts index 9a918e0..5251b75 100644 --- a/src/components/task-manager/components/list/types.ts +++ b/src/components/task-manager/components/list/types.ts @@ -2,6 +2,6 @@ import type { Task } from "../../store/models"; export interface TasksListProps { tasks: Task[]; - onDelete: (id: number) => void; - onToggle: (id: number) => void; + onDelete: (id: string) => void; + onToggle: (id: string) => void; } diff --git a/src/components/task-manager/components/manager/TaskManager.tsx b/src/components/task-manager/components/manager/TaskManager.tsx index 5fb69f5..d203da7 100644 --- a/src/components/task-manager/components/manager/TaskManager.tsx +++ b/src/components/task-manager/components/manager/TaskManager.tsx @@ -1,17 +1,24 @@ -import React from "react"; +import React, { useEffect } from "react"; -import { useTaskStore } from "../../store"; +import { useTasksStore } from "../../store"; import { TaskFilter, TaskForm, TasksList } from ".."; +import { LoadingIndicator } from "../../../design-system"; export const TaskManager: React.FC = () => { - const { + const { filteredTasks, filter, addTask, deleteTask, toggleTask, - setFilter - } = useTaskStore(); + setFilter, + fetchTasks, + isLoading + } = useTasksStore(); + + useEffect(() => { + fetchTasks(); + }, [fetchTasks]); return (
    @@ -22,11 +29,15 @@ export const TaskManager: React.FC = () => { onFilterChange={setFilter} /> - + {isLoading ? ( + + ) : ( + + )}
    ); }; diff --git a/src/components/task-manager/store/helpers.tsx b/src/components/task-manager/store/helpers.tsx index b400343..08ad51a 100644 --- a/src/components/task-manager/store/helpers.tsx +++ b/src/components/task-manager/store/helpers.tsx @@ -7,9 +7,3 @@ export const getFilteredTasks = (tasks: Task[], filter: FilterType): Task[] => { return true; }); }; - -export const generateTaskId = (tasks: Task[]): number => { - return tasks.length > 0 - ? Math.max(...tasks.map(task => task.id)) + 1 - : 1; -}; diff --git a/src/components/task-manager/store/index.ts b/src/components/task-manager/store/index.ts index 4086a6d..6c5dcd3 100644 --- a/src/components/task-manager/store/index.ts +++ b/src/components/task-manager/store/index.ts @@ -1,2 +1,2 @@ export * from './models'; -export { useTasksStore as useTaskStore } from './taskStore'; +export { useTasksStore } from './taskStore'; diff --git a/src/components/task-manager/store/models.ts b/src/components/task-manager/store/models.ts index 340db72..77a96db 100644 --- a/src/components/task-manager/store/models.ts +++ b/src/components/task-manager/store/models.ts @@ -1,5 +1,5 @@ export interface Task { - id: number; + id: string; title: string; completed: boolean; } diff --git a/src/components/task-manager/store/service.ts b/src/components/task-manager/store/service.ts new file mode 100644 index 0000000..4772c30 --- /dev/null +++ b/src/components/task-manager/store/service.ts @@ -0,0 +1,61 @@ +import { supabase } from '../../../lib/supabase'; +import type { Task } from './models'; + +export async function fetchTasks(): Promise { + const { data, error } = await supabase + .from('tasks') + .select('*') + .order('id', { ascending: true }); + + if (error) { + console.error('Error fetching tasks:', error); + return []; + } + + return data || []; +} + +export async function addTask(task: Omit): Promise { + const { data, error } = await supabase + .from('tasks') + .insert([task]) + .select() + .single(); + + if (error) { + console.error('Error adding task:', error); + return null; + } + + return data; +} + +export async function updateTask(task: Task): Promise { + const { data, error } = await supabase + .from('tasks') + .update({ title: task.title, completed: task.completed }) + .eq('id', task.id) + .select() + .single(); + + if (error) { + console.error('Error updating task:', error); + return null; + } + + return data; +} + +export async function deleteTask(id: string): Promise { + const { error } = await supabase + .from('tasks') + .delete() + .eq('id', id); + + if (error) { + console.error('Error deleting task:', error); + return false; + } + + return true; +} diff --git a/src/components/task-manager/store/taskStore.ts b/src/components/task-manager/store/taskStore.ts index 4598c54..a8c2b7a 100644 --- a/src/components/task-manager/store/taskStore.ts +++ b/src/components/task-manager/store/taskStore.ts @@ -1,55 +1,122 @@ import { create } from 'zustand'; -import type { TaskState } from './types'; +import type { TasksState } from './types'; import type { FilterType, Task } from './models'; -import { generateTaskId, getFilteredTasks } from './helpers'; +import { getFilteredTasks } from './helpers'; +import { fetchTasks, addTask, updateTask, deleteTask } from './service'; -const initialTasks: Task[] = [ - { id: 1, title: "Buy groceries", completed: false }, - { id: 2, title: "Clean the house", completed: true }, -]; +const initialTasks: Task[] = []; -export const useTasksStore = create((set, get) => ({ +export const useTasksStore = create((set, get) => ({ tasks: initialTasks, filter: 'all', filteredTasks: initialTasks, + isLoading: false, - addTask: (title: string) => { - const { tasks } = get(); - const newTask: Task = { - id: generateTaskId(tasks), + fetchTasks: async () => { + set({ isLoading: true }); + + const tasks = await fetchTasks(); + + set(state => ({ + tasks, + filteredTasks: getFilteredTasks(tasks, state.filter), + isLoading: false + })); + }, + + addTask: async (title: string) => { + const newTaskData = { title, completed: false, }; - + + // temporary id for optimistic updates + const tempId = `temp-${Date.now()}`; + set(state => { - const updatedTasks = [...state.tasks, newTask]; + const updatedTasks = [ + ...state.tasks, + { ...newTaskData, id: tempId } + ]; + return { tasks: updatedTasks, filteredTasks: getFilteredTasks(updatedTasks, state.filter) }; }); + + const persistedTask = await addTask(newTaskData); + + if (persistedTask) { + set(state => { + const updatedTasks = state.tasks.map(task => + task.id === tempId ? persistedTask : task + ); + return { + tasks: updatedTasks, + filteredTasks: getFilteredTasks(updatedTasks, state.filter) + }; + }); + } }, - deleteTask: (id: number) => { + deleteTask: async (id: string) => { + // optimistically update UI + const taskToDelete = get().tasks.find(task => task.id === id); + set(state => { const updatedTasks = state.tasks.filter(task => task.id !== id); + return { tasks: updatedTasks, filteredTasks: getFilteredTasks(updatedTasks, state.filter) }; }); + + const success = await deleteTask(id); + + // if there was a problem deleting, then revert it + if (!success && taskToDelete) { + set(state => { + const updatedTasks = [...state.tasks, taskToDelete]; + + return { + tasks: updatedTasks, + filteredTasks: getFilteredTasks(updatedTasks, state.filter) + }; + }); + } }, - toggleTask: (id: number) => { + toggleTask: async (id: string) => { + const taskToUpdate = get().tasks.find(task => task.id === id); + if (!taskToUpdate) return; + + const updatedTask = { ...taskToUpdate, completed: !taskToUpdate.completed }; + set(state => { const updatedTasks = state.tasks.map(task => - task.id === id ? { ...task, completed: !task.completed } : task + task.id === id ? updatedTask : task ); return { tasks: updatedTasks, filteredTasks: getFilteredTasks(updatedTasks, state.filter) }; }); + + const result = await updateTask(updatedTask); + + if (!result) { + set(state => { + const revertedTasks = state.tasks.map(task => + task.id === id ? taskToUpdate : task + ); + return { + tasks: revertedTasks, + filteredTasks: getFilteredTasks(revertedTasks, state.filter) + }; + }); + } }, setFilter: (filter: FilterType) => { diff --git a/src/components/task-manager/store/types.ts b/src/components/task-manager/store/types.ts index b8e3fb1..2f91930 100644 --- a/src/components/task-manager/store/types.ts +++ b/src/components/task-manager/store/types.ts @@ -1,11 +1,13 @@ import type { Task, FilterType } from './models'; -export interface TaskState { +export interface TasksState { tasks: Task[]; filter: FilterType; filteredTasks: Task[]; + isLoading: boolean; + fetchTasks: () => void; addTask: (title: string) => void; - deleteTask: (id: number) => void; - toggleTask: (id: number) => void; + deleteTask: (id: string) => void; + toggleTask: (id: string) => void; setFilter: (filter: FilterType) => void; } diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts new file mode 100644 index 0000000..90eab0c --- /dev/null +++ b/src/lib/supabase.ts @@ -0,0 +1,6 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || ''; +const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || ''; + +export const supabase = createClient(supabaseUrl, supabaseAnonKey); From 8394ef923bcf4f20dd92ef948745bbb4189a5ad1 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 19:33:39 -0300 Subject: [PATCH 09/25] feat: implement confirmation dialog for delete confirmation --- package.json | 2 +- .../ConfirmDialog/ConfirmDialog.tsx | 39 +++++++++++++++++++ .../design-system/ConfirmDialog/index.ts | 2 + .../design-system/ConfirmDialog/types.tsx | 11 ++++++ src/components/design-system/index.ts | 1 + .../task-manager/components/item/TaskItem.tsx | 27 +++++++++++-- .../task-manager/components/item/types.ts | 4 +- 7 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 src/components/design-system/ConfirmDialog/ConfirmDialog.tsx create mode 100644 src/components/design-system/ConfirmDialog/index.ts create mode 100644 src/components/design-system/ConfirmDialog/types.tsx diff --git a/package.json b/package.json index 73d8278..ab3f2b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.0.8", + "version": "0.0.9", "private": true, "scripts": { "dev": "vite", diff --git a/src/components/design-system/ConfirmDialog/ConfirmDialog.tsx b/src/components/design-system/ConfirmDialog/ConfirmDialog.tsx new file mode 100644 index 0000000..daa2d59 --- /dev/null +++ b/src/components/design-system/ConfirmDialog/ConfirmDialog.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import type { ConfirmDialogProps } from "./types"; +import { Button } from "../Button"; + +export const ConfirmDialog: React.FC = ({ + isOpen, + title, + message, + onCancel, + onConfirm, + cancelLabel = "Cancel", + confirmLabel = "Confirm", +}) => { + if (!isOpen) return null; + + return ( +
    +
    +

    {title}

    +

    {message}

    + +
    + + +
    +
    +
    + ); +}; diff --git a/src/components/design-system/ConfirmDialog/index.ts b/src/components/design-system/ConfirmDialog/index.ts new file mode 100644 index 0000000..de6dcaf --- /dev/null +++ b/src/components/design-system/ConfirmDialog/index.ts @@ -0,0 +1,2 @@ +export * from "./ConfirmDialog"; +export * from "./types"; diff --git a/src/components/design-system/ConfirmDialog/types.tsx b/src/components/design-system/ConfirmDialog/types.tsx new file mode 100644 index 0000000..1c196df --- /dev/null +++ b/src/components/design-system/ConfirmDialog/types.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +export interface ConfirmDialogProps { + isOpen: boolean; + title: string; + message: string; + confirmLabel?: string; + cancelLabel?: string; + onCancel: () => void; + onConfirm: () => void; +} diff --git a/src/components/design-system/index.ts b/src/components/design-system/index.ts index 0852388..409a591 100644 --- a/src/components/design-system/index.ts +++ b/src/components/design-system/index.ts @@ -1,3 +1,4 @@ export * from "./Button"; export * from "./Input"; export * from "./LoadingIndicator"; +export * from "./ConfirmDialog"; diff --git a/src/components/task-manager/components/item/TaskItem.tsx b/src/components/task-manager/components/item/TaskItem.tsx index fe30f0c..504d5ed 100644 --- a/src/components/task-manager/components/item/TaskItem.tsx +++ b/src/components/task-manager/components/item/TaskItem.tsx @@ -1,8 +1,19 @@ -import React from "react"; +import React, { useState } from "react"; import type { TaskItemProps } from "./types"; -import { Button } from "../../../design-system"; +import { Button, ConfirmDialog } from "../../../design-system"; export const TaskItem: React.FC = ({ task, onDelete, onToggle }) => { + const [showConfirmDialog, setShowConfirmDialog] = useState(false); + + const toggleConfirmDialog = () => { + setShowConfirmDialog((open) => !open); + }; + + const handleConfirmDelete = () => { + onDelete(task.id); + toggleConfirmDialog(); + }; + return (
  • @@ -19,10 +30,20 @@ export const TaskItem: React.FC = ({ task, onDelete, onToggle }) + +
  • ); }; diff --git a/src/components/task-manager/components/item/types.ts b/src/components/task-manager/components/item/types.ts index d4db569..63ff766 100644 --- a/src/components/task-manager/components/item/types.ts +++ b/src/components/task-manager/components/item/types.ts @@ -2,6 +2,6 @@ import type { Task } from "../../store/models"; export interface TaskItemProps { task: Task; - onDelete: (id: number) => void; - onToggle: (id: number) => void; + onDelete: (id: string) => void; + onToggle: (id: string) => void; } From 3e191e77e85df416145e8fec6ccf8dc502953648 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 20:04:40 -0300 Subject: [PATCH 10/25] feat: added toast notifications for task deletion --- package.json | 2 +- src/App.tsx | 15 ++++--- src/components/design-system/Toast/Toast.tsx | 29 +++++++++++++ .../design-system/Toast/ToastProvider.tsx | 42 +++++++++++++++++++ .../design-system/Toast/constants.ts | 1 + src/components/design-system/Toast/index.ts | 3 ++ src/components/design-system/Toast/styles.tsx | 9 ++++ src/components/design-system/Toast/types.tsx | 11 +++++ src/components/design-system/index.ts | 1 + .../task-manager/components/item/TaskItem.tsx | 4 +- 10 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 src/components/design-system/Toast/Toast.tsx create mode 100644 src/components/design-system/Toast/ToastProvider.tsx create mode 100644 src/components/design-system/Toast/constants.ts create mode 100644 src/components/design-system/Toast/index.ts create mode 100644 src/components/design-system/Toast/styles.tsx create mode 100644 src/components/design-system/Toast/types.tsx diff --git a/package.json b/package.json index ab3f2b7..bbf4d5a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.0.9", + "version": "0.10.0", "private": true, "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index 2af91a9..3029f92 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,18 @@ import React from "react"; import { TaskManager } from "./components/task-manager"; +import { ToastProvider } from "./components/design-system"; export function App() { return ( -
    -
    -

    Task Manager

    -
    + +
    +
    +

    Task Manager

    +
    - -
    + +
    + ); } diff --git a/src/components/design-system/Toast/Toast.tsx b/src/components/design-system/Toast/Toast.tsx new file mode 100644 index 0000000..e61223c --- /dev/null +++ b/src/components/design-system/Toast/Toast.tsx @@ -0,0 +1,29 @@ +import React, { useEffect } from "react"; + +import { ToastProps } from "./types"; +import { colorClasses, toastClasses } from "./styles"; +import { TOAST_AUTO_CLOSE_TIMEOUT } from "./constants"; + +export const Toast: React.FC = ({ + message, + onClose, + type = "success", +}) => { + useEffect(() => { + const timer = setTimeout(onClose, TOAST_AUTO_CLOSE_TIMEOUT); + + return () => clearTimeout(timer); + }, [onClose]); + + return ( +
    + {message} + +
    + ); +}; diff --git a/src/components/design-system/Toast/ToastProvider.tsx b/src/components/design-system/Toast/ToastProvider.tsx new file mode 100644 index 0000000..043a19d --- /dev/null +++ b/src/components/design-system/Toast/ToastProvider.tsx @@ -0,0 +1,42 @@ +import React, { createContext, useState, useContext } from 'react'; +import type { ToastContextProps, ToastType } from './types'; +import { Toast } from './Toast'; + +const ToastContext = createContext(undefined); + +export const useToast = () => { + const context = useContext(ToastContext); + if (!context) { + throw new Error('useToast must be used within a ToastProvider'); + } + return context; +}; + +export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [toasts, setToasts] = useState<{ id: string; message: string; type: ToastType }[]>([]); + + const showToast = (message: string, type: ToastType = 'success') => { + const id = `toast-${Date.now()}`; + setToasts((prevToasts) => [...prevToasts, { id, message, type }]); + }; + + const closeToast = (id: string) => { + setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id)); + }; + + return ( + + {children} +
    + {toasts.map(({ id, message, type }) => ( + closeToast(id)} + /> + ))} +
    +
    + ); +}; diff --git a/src/components/design-system/Toast/constants.ts b/src/components/design-system/Toast/constants.ts new file mode 100644 index 0000000..794894e --- /dev/null +++ b/src/components/design-system/Toast/constants.ts @@ -0,0 +1 @@ +export const TOAST_AUTO_CLOSE_TIMEOUT = 3000; // 3 seconds diff --git a/src/components/design-system/Toast/index.ts b/src/components/design-system/Toast/index.ts new file mode 100644 index 0000000..0e2c6a2 --- /dev/null +++ b/src/components/design-system/Toast/index.ts @@ -0,0 +1,3 @@ +export * from './Toast'; +export * from './ToastProvider'; +export * from './types'; diff --git a/src/components/design-system/Toast/styles.tsx b/src/components/design-system/Toast/styles.tsx new file mode 100644 index 0000000..1147d7b --- /dev/null +++ b/src/components/design-system/Toast/styles.tsx @@ -0,0 +1,9 @@ +export const toastClasses = + 'fixed bottom-4 right-4 text-white px-6 py-3 rounded shadow-lg flex items-center justify-between min-w-[250px] transform transition-all duration-500 ease-in-out'; + +export const colorClasses = { + success: "bg-green-500", + error: "bg-red-500", + warning: "bg-yellow-500", + info: "bg-blue-500", +}; diff --git a/src/components/design-system/Toast/types.tsx b/src/components/design-system/Toast/types.tsx new file mode 100644 index 0000000..2f1fb2e --- /dev/null +++ b/src/components/design-system/Toast/types.tsx @@ -0,0 +1,11 @@ +export type ToastType = "success" | "error" | "warning" | "info"; + +export interface ToastProps { + message: string; + type?: ToastType; + onClose: () => void; +} + +export interface ToastContextProps { + showToast: (message: string, type?: ToastType) => void; +} diff --git a/src/components/design-system/index.ts b/src/components/design-system/index.ts index 409a591..5a61d6e 100644 --- a/src/components/design-system/index.ts +++ b/src/components/design-system/index.ts @@ -2,3 +2,4 @@ export * from "./Button"; export * from "./Input"; export * from "./LoadingIndicator"; export * from "./ConfirmDialog"; +export * from "./Toast"; diff --git a/src/components/task-manager/components/item/TaskItem.tsx b/src/components/task-manager/components/item/TaskItem.tsx index 504d5ed..c4cba8f 100644 --- a/src/components/task-manager/components/item/TaskItem.tsx +++ b/src/components/task-manager/components/item/TaskItem.tsx @@ -1,9 +1,10 @@ import React, { useState } from "react"; import type { TaskItemProps } from "./types"; -import { Button, ConfirmDialog } from "../../../design-system"; +import { Button, ConfirmDialog, useToast } from "../../../design-system"; export const TaskItem: React.FC = ({ task, onDelete, onToggle }) => { const [showConfirmDialog, setShowConfirmDialog] = useState(false); + const { showToast } = useToast(); const toggleConfirmDialog = () => { setShowConfirmDialog((open) => !open); @@ -12,6 +13,7 @@ export const TaskItem: React.FC = ({ task, onDelete, onToggle }) const handleConfirmDelete = () => { onDelete(task.id); toggleConfirmDialog(); + showToast(`Task "${task.title}" deleted successfully`); }; return ( From 6661332ec763db7ad2b0ae7407a915b41b2a900b Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Thu, 10 Apr 2025 20:25:49 -0300 Subject: [PATCH 11/25] feat: enhanced UI better responsiveness --- package.json | 2 +- .../task-manager/components/filter/TaskFilter.tsx | 5 ++++- src/components/task-manager/components/form/TaskForm.tsx | 6 +++--- src/components/task-manager/components/item/TaskItem.tsx | 8 ++++---- .../task-manager/components/manager/TaskManager.tsx | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index bbf4d5a..c824cdb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-manager", - "version": "0.10.0", + "version": "0.11.0", "private": true, "scripts": { "dev": "vite", diff --git a/src/components/task-manager/components/filter/TaskFilter.tsx b/src/components/task-manager/components/filter/TaskFilter.tsx index 1e20c5f..9ae7bb8 100644 --- a/src/components/task-manager/components/filter/TaskFilter.tsx +++ b/src/components/task-manager/components/filter/TaskFilter.tsx @@ -7,22 +7,25 @@ export const TaskFilter: React.FC = ({ onFilterChange }) => { return ( -
    +
    diff --git a/src/components/task-manager/components/form/TaskForm.tsx b/src/components/task-manager/components/form/TaskForm.tsx index 18c4b52..a610666 100644 --- a/src/components/task-manager/components/form/TaskForm.tsx +++ b/src/components/task-manager/components/form/TaskForm.tsx @@ -14,16 +14,16 @@ export const TaskForm: React.FC = ({ onAddTask }) => { }, [newTask, onAddTask]); return ( -
    + setNewTask(e.target.value)} /> diff --git a/src/components/task-manager/components/item/TaskItem.tsx b/src/components/task-manager/components/item/TaskItem.tsx index c4cba8f..59d65d9 100644 --- a/src/components/task-manager/components/item/TaskItem.tsx +++ b/src/components/task-manager/components/item/TaskItem.tsx @@ -17,11 +17,11 @@ export const TaskItem: React.FC = ({ task, onDelete, onToggle }) }; return ( -
  • -
    +
  • +
    onToggle(task.id)} - className={`cursor-pointer ${ + className={`cursor-pointer text-sm sm:text-base truncate ${ task.completed ? "line-through text-green-500" : "text-black" }`} > @@ -31,7 +31,7 @@ export const TaskItem: React.FC = ({ task, onDelete, onToggle }) ); + + const button = await getByRole('button', { name: /click me/i }); + await expect.element(button).toBeInTheDocument(); + await expect.element(button).toHaveAttribute('type', 'button'); + }); + + it.each( + ['primary', 'secondary', 'danger'] as ButtonProps['variant'][] + )('renders with %s variant', async (variant) => { + const { getByRole } = render( + + ); + + const button = await getByRole('button', { name: variant }); + await expect.element(button).toBeInTheDocument(); + }); + + it('renders with custom type', async () => { + const { getByRole } = render(); + + const button = await getByRole('button', { name: /submit/i }); + await expect.element(button).toHaveAttribute('type', 'submit'); + }); + + it('triggers onClick when clicked', async () => { + const handleClick = vi.fn(); + const { getByRole } = render(); + + const button = await getByRole('button', { name: /click me/i }); + await button.click(); + + expect(handleClick).toHaveBeenCalledTimes(1); + }); + + it('is disabled when disabled prop is true', async () => { + const { getByRole } = render(); + + const button = await getByRole('button', { name: /disabled/i }); + await expect.element(button).toBeDisabled(); + }); +}); diff --git a/src/components/design-system/ConfirmDialog/ConfirmDialog.spec.tsx b/src/components/design-system/ConfirmDialog/ConfirmDialog.spec.tsx new file mode 100644 index 0000000..e0ccb7e --- /dev/null +++ b/src/components/design-system/ConfirmDialog/ConfirmDialog.spec.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render } from 'vitest-browser-react'; +import { ConfirmDialog } from './ConfirmDialog'; + +describe('ConfirmDialog', () => { + const defaultProps = { + isOpen: true, + title: 'Confirm Action', + message: 'Are you sure you want to proceed?', + onCancel: vi.fn(), + onConfirm: vi.fn() + }; + + beforeEach(vi.clearAllMocks); + + it('renders when isOpen is true', async () => { + const { getByText, getByRole } = render(); + + await expect.element(await getByText('Confirm Action')).toBeInTheDocument(); + await expect.element(await getByText('Are you sure you want to proceed?')).toBeInTheDocument(); + await expect.element(await getByRole('button', { name: /cancel/i })).toBeInTheDocument(); + await expect.element(await getByRole('button', { name: /confirm/i })).toBeInTheDocument(); + }); + + it('does not render when isOpen is false', async () => { + const { getByText } = render(); + + await expect.element(await getByText('Confirm Action')).not.toBeInTheDocument(); + await expect.element(await getByText('Are you sure you want to proceed?')).not.toBeInTheDocument(); + }); + + it('calls onCancel when cancel button is clicked', async () => { + const { getByRole } = render(); + + const cancelButton = await getByRole('button', { name: /cancel/i }); + await cancelButton.click(); + + expect(defaultProps.onCancel).toHaveBeenCalledTimes(1); + expect(defaultProps.onConfirm).not.toHaveBeenCalled(); + }); + + it('calls onConfirm when confirm button is clicked', async () => { + const { getByRole } = render(); + + const confirmButton = await getByRole('button', { name: /confirm/i }); + await confirmButton.click(); + + expect(defaultProps.onConfirm).toHaveBeenCalledTimes(1); + expect(defaultProps.onCancel).not.toHaveBeenCalled(); + }); + + it('displays custom button labels when provided', async () => { + const { getByRole } = render( + + ); + + await expect.element(await getByRole('button', { name: /no, go back/i })).toBeInTheDocument(); + await expect.element(await getByRole('button', { name: /yes, do it/i })).toBeInTheDocument(); + }); +}); diff --git a/src/components/design-system/Input/Input.spec.tsx b/src/components/design-system/Input/Input.spec.tsx new file mode 100644 index 0000000..d25f770 --- /dev/null +++ b/src/components/design-system/Input/Input.spec.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { userEvent } from '@vitest/browser/context' +import { render } from 'vitest-browser-react'; +import { Input } from './Input'; +import { InputProps } from './types'; + +describe('Input', () => { + const handleChange = vi.fn(); + + beforeEach(vi.clearAllMocks); + + it('renders correctly with default props', async () => { + const { getByRole } = render(); + + const input = await getByRole('textbox'); + await expect.element(input).toBeInTheDocument(); + await expect.element(input).toHaveAttribute('type', 'text'); + await expect.element(input).toHaveValue(''); + }); + + it('displays the provided value', async () => { + const { getByRole } = render(); + + const input = await getByRole('textbox'); + await expect.element(input).toHaveValue('Test value'); + }); + + it('calls onChange handler when input changes', async () => { + const StatefullInput = (props: InputProps) => { + const [value, setValue] = React.useState(props.value); + return { setValue(e.target.value); props.onChange?.(e); }} />; + }; + + const { getByRole } = render(); + + const input = await getByRole('textbox'); + + await expect(input).toHaveValue('initial value'); + await expect(handleChange).not.toHaveBeenCalled(); + + await userEvent.clear(input); + await userEvent.type(input, 'New value'); + + await expect(input).toHaveValue('New value'); + await expect(handleChange).toHaveBeenCalled(); + + const calls = handleChange.mock.calls; + await expect(calls[calls.length - 1][0].target.value).toBe('New value'); + }); + + it('applies custom type', async () => { + const { getByRole } = render(); + + const input = await getByRole('textbox'); + await expect.element(input).toHaveAttribute('type', 'password'); + }); + + it('applies placeholder text', async () => { + const { getByPlaceholder } = render(); + + const input = await getByPlaceholder('Enter your name'); + await expect.element(input).toBeInTheDocument(); + }); +}); diff --git a/src/components/design-system/LoadingIndicator/LoadingIndicator.spec.tsx b/src/components/design-system/LoadingIndicator/LoadingIndicator.spec.tsx new file mode 100644 index 0000000..6a962c7 --- /dev/null +++ b/src/components/design-system/LoadingIndicator/LoadingIndicator.spec.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { describe, it, expect } from 'vitest'; +import { render } from 'vitest-browser-react'; +import { LoadingIndicator } from './LoadingIndicator'; + +describe('LoadingIndicator', () => { + it('renders correctly', async () => { + const { getByRole } = render(); + + const loadingElement = await getByRole('status'); + await expect.element(loadingElement).toBeInTheDocument(); + }); +}); diff --git a/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx b/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx index ebcef40..b256e57 100644 --- a/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx +++ b/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx @@ -1,7 +1,7 @@ import React from "react"; export const LoadingIndicator = () => ( -
    +
    ); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..70a2bb5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["esnext", "dom"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "types": ["@vitest/browser/providers/playwright"], + "strict": true, + "declaration": true, + "noEmit": true, + "esModuleInterop": true, + "skipLibCheck": true + } + } \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..89f1385 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + test: { + browser: { + enabled: true, + provider: 'playwright', + instances: [ + { browser: 'chromium' }, + ], + } + } +}) \ No newline at end of file diff --git a/vitest.workspace.ts b/vitest.workspace.ts new file mode 100644 index 0000000..410e6fd --- /dev/null +++ b/vitest.workspace.ts @@ -0,0 +1,18 @@ +import { defineWorkspace } from 'vitest/config' + +export default defineWorkspace([ + // If you want to keep running your existing tests in Node.js, uncomment the next line. + // 'vitest.config.ts', + { + extends: 'vitest.config.ts', + test: { + browser: { + enabled: true, + provider: 'playwright', + // https://vitest.dev/guide/browser/playwright + instances: [ + ], + }, + }, + }, +]) From 4e989e67ade3d43c5db9ac29741649a58ab88d86 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 20:16:52 -0300 Subject: [PATCH 16/25] refactor: structures it as a monorepo, adds more tests, configures github action for vercel deployment --- .eslintrc.json | 53 + .github/workflows/deploy-to-vercel.yml | 62 + .gitignore | 4 +- .prettierrc.json | 10 + eslint.config.js | 80 + package.json | 45 +- packages/design-system/README.md | 34 + packages/design-system/package.json | 23 + .../design-system/src}/Button/Button.spec.tsx | 19 +- .../design-system/src}/Button/Button.tsx | 12 +- packages/design-system/src/Button/index.ts | 2 + packages/design-system/src/Button/styles.tsx | 9 + .../design-system/src}/Button/types.tsx | 6 +- .../src}/ConfirmDialog/ConfirmDialog.spec.tsx | 26 +- .../src}/ConfirmDialog/ConfirmDialog.tsx | 24 +- .../design-system/src/ConfirmDialog/index.ts | 2 + .../src}/ConfirmDialog/types.tsx | 2 - .../design-system/src}/Input/Input.spec.tsx | 21 +- .../design-system/src}/Input/Input.tsx | 12 +- packages/design-system/src/Input/index.ts | 2 + packages/design-system/src/Input/styles.tsx | 2 + .../design-system/src}/Input/types.tsx | 2 +- .../LoadingIndicator.spec.tsx | 2 +- .../src/LoadingIndicator/LoadingIndicator.tsx | 12 + .../src}/LoadingIndicator/index.ts | 0 packages/design-system/src/Toast/Toast.tsx | 22 + .../src}/Toast/ToastProvider.tsx | 14 +- .../design-system/src}/Toast/constants.ts | 0 .../design-system/src}/Toast/index.ts | 1 + .../design-system/src}/Toast/styles.tsx | 10 +- .../design-system/src}/Toast/types.tsx | 2 +- packages/design-system/src/index.ts | 5 + packages/design-system/tsconfig.json | 21 + packages/task-manager/README.md | 39 + .../task-manager/index.html | 0 packages/task-manager/package.json | 37 + .../task-manager/postcss.config.js | 0 {src => packages/task-manager/src}/App.tsx | 11 +- .../src/components/filter/TaskFilter.spec.tsx | 38 + .../src/components/filter/TaskFilter.tsx | 32 + .../src/components/filter/index.ts | 1 + .../src}/components/filter/types.tsx | 2 +- .../src/components/form/TaskForm.spec.tsx | 71 + .../src/components/form/TaskForm.tsx | 41 + .../task-manager/src/components/form/index.ts | 1 + .../src}/components/form/types.tsx | 0 packages/task-manager/src/components/index.ts | 5 + .../src/components/item/TaskItem.spec.tsx | 111 + .../src}/components/item/TaskItem.tsx | 17 +- .../task-manager/src/components/item/index.ts | 2 + .../src}/components/item/types.ts | 2 +- .../src/components/list/TasksList.spec.tsx | 51 + .../src/components/list/TasksList.tsx | 19 + .../task-manager/src/components/list/index.ts | 2 + .../src}/components/list/types.ts | 2 +- .../components/manager/TaskManager.spec.tsx | 113 + .../src}/components/manager/TaskManager.tsx | 25 +- .../src/components/manager/index.ts | 1 + {src => packages/task-manager/src}/index.css | 2 +- .../task-manager/src}/lib/supabase.ts | 0 packages/task-manager/src/main.tsx | 11 + packages/task-manager/src/store/helpers.tsx | 9 + packages/task-manager/src/store/index.ts | 4 + .../task-manager/src}/store/models.ts | 0 .../task-manager/src}/store/service.ts | 14 +- .../task-manager/src}/store/taskStore.ts | 40 +- .../task-manager/src}/store/types.ts | 0 .../task-manager/tailwind.config.js | 6 +- packages/task-manager/tsconfig.json | 20 + packages/task-manager/vite.config.ts | 14 + packages/task-manager/vitest.config.ts | 16 + pnpm-lock.yaml | 2056 ++++++++++++++++- pnpm-workspace.yaml | 2 + src/components/design-system/Button/index.ts | 1 - .../design-system/Button/styles.tsx | 7 - .../design-system/ConfirmDialog/index.ts | 2 - src/components/design-system/Input/index.ts | 1 - src/components/design-system/Input/styles.tsx | 1 - .../LoadingIndicator/LoadingIndicator.tsx | 7 - src/components/design-system/README.md | 3 - src/components/design-system/Toast/Toast.tsx | 29 - src/components/design-system/index.ts | 5 - .../components/filter/TaskFilter.tsx | 34 - .../task-manager/components/filter/index.ts | 1 - .../task-manager/components/form/TaskForm.tsx | 34 - .../task-manager/components/form/index.ts | 1 - .../task-manager/components/index.ts | 5 - .../components/list/TasksList.tsx | 27 - .../task-manager/components/list/index.ts | 2 - src/components/task-manager/index.ts | 1 - src/components/task-manager/store/helpers.tsx | 9 - src/components/task-manager/store/index.ts | 2 - src/main.tsx | 12 - tsconfig.json | 15 - vite.config.ts | 6 - vitest.config.ts | 11 +- vitest.workspace.ts | 3 + 97 files changed, 3194 insertions(+), 383 deletions(-) create mode 100644 .eslintrc.json create mode 100644 .github/workflows/deploy-to-vercel.yml create mode 100644 .prettierrc.json create mode 100644 eslint.config.js create mode 100644 packages/design-system/README.md create mode 100644 packages/design-system/package.json rename {src/components/design-system => packages/design-system/src}/Button/Button.spec.tsx (79%) rename {src/components/design-system => packages/design-system/src}/Button/Button.tsx (60%) create mode 100644 packages/design-system/src/Button/index.ts create mode 100644 packages/design-system/src/Button/styles.tsx rename {src/components/design-system => packages/design-system/src}/Button/types.tsx (53%) rename {src/components/design-system => packages/design-system/src}/ConfirmDialog/ConfirmDialog.spec.tsx (88%) rename {src/components/design-system => packages/design-system/src}/ConfirmDialog/ConfirmDialog.tsx (63%) create mode 100644 packages/design-system/src/ConfirmDialog/index.ts rename {src/components/design-system => packages/design-system/src}/ConfirmDialog/types.tsx (87%) rename {src/components/design-system => packages/design-system/src}/Input/Input.spec.tsx (80%) rename {src/components/design-system => packages/design-system/src}/Input/Input.tsx (58%) create mode 100644 packages/design-system/src/Input/index.ts create mode 100644 packages/design-system/src/Input/styles.tsx rename {src/components/design-system => packages/design-system/src}/Input/types.tsx (86%) rename {src/components/design-system => packages/design-system/src}/LoadingIndicator/LoadingIndicator.spec.tsx (98%) create mode 100644 packages/design-system/src/LoadingIndicator/LoadingIndicator.tsx rename {src/components/design-system => packages/design-system/src}/LoadingIndicator/index.ts (100%) create mode 100644 packages/design-system/src/Toast/Toast.tsx rename {src/components/design-system => packages/design-system/src}/Toast/ToastProvider.tsx (77%) rename {src/components/design-system => packages/design-system/src}/Toast/constants.ts (100%) rename {src/components/design-system => packages/design-system/src}/Toast/index.ts (74%) rename {src/components/design-system => packages/design-system/src}/Toast/styles.tsx (60%) rename {src/components/design-system => packages/design-system/src}/Toast/types.tsx (75%) create mode 100644 packages/design-system/src/index.ts create mode 100644 packages/design-system/tsconfig.json create mode 100644 packages/task-manager/README.md rename index.html => packages/task-manager/index.html (100%) create mode 100644 packages/task-manager/package.json rename postcss.config.js => packages/task-manager/postcss.config.js (100%) rename {src => packages/task-manager/src}/App.tsx (59%) create mode 100644 packages/task-manager/src/components/filter/TaskFilter.spec.tsx create mode 100644 packages/task-manager/src/components/filter/TaskFilter.tsx create mode 100644 packages/task-manager/src/components/filter/index.ts rename {src/components/task-manager => packages/task-manager/src}/components/filter/types.tsx (68%) create mode 100644 packages/task-manager/src/components/form/TaskForm.spec.tsx create mode 100644 packages/task-manager/src/components/form/TaskForm.tsx create mode 100644 packages/task-manager/src/components/form/index.ts rename {src/components/task-manager => packages/task-manager/src}/components/form/types.tsx (100%) create mode 100644 packages/task-manager/src/components/index.ts create mode 100644 packages/task-manager/src/components/item/TaskItem.spec.tsx rename {src/components/task-manager => packages/task-manager/src}/components/item/TaskItem.tsx (81%) create mode 100644 packages/task-manager/src/components/item/index.ts rename {src/components/task-manager => packages/task-manager/src}/components/item/types.ts (71%) create mode 100644 packages/task-manager/src/components/list/TasksList.spec.tsx create mode 100644 packages/task-manager/src/components/list/TasksList.tsx create mode 100644 packages/task-manager/src/components/list/index.ts rename {src/components/task-manager => packages/task-manager/src}/components/list/types.ts (71%) create mode 100644 packages/task-manager/src/components/manager/TaskManager.spec.tsx rename {src/components/task-manager => packages/task-manager/src}/components/manager/TaskManager.tsx (54%) create mode 100644 packages/task-manager/src/components/manager/index.ts rename {src => packages/task-manager/src}/index.css (64%) rename {src => packages/task-manager/src}/lib/supabase.ts (100%) create mode 100644 packages/task-manager/src/main.tsx create mode 100644 packages/task-manager/src/store/helpers.tsx create mode 100644 packages/task-manager/src/store/index.ts rename {src/components/task-manager => packages/task-manager/src}/store/models.ts (100%) rename {src/components/task-manager => packages/task-manager/src}/store/service.ts (80%) rename {src/components/task-manager => packages/task-manager/src}/store/taskStore.ts (82%) rename {src/components/task-manager => packages/task-manager/src}/store/types.ts (100%) rename tailwind.config.js => packages/task-manager/tailwind.config.js (51%) create mode 100644 packages/task-manager/tsconfig.json create mode 100644 packages/task-manager/vite.config.ts create mode 100644 packages/task-manager/vitest.config.ts create mode 100644 pnpm-workspace.yaml delete mode 100644 src/components/design-system/Button/index.ts delete mode 100644 src/components/design-system/Button/styles.tsx delete mode 100644 src/components/design-system/ConfirmDialog/index.ts delete mode 100644 src/components/design-system/Input/index.ts delete mode 100644 src/components/design-system/Input/styles.tsx delete mode 100644 src/components/design-system/LoadingIndicator/LoadingIndicator.tsx delete mode 100644 src/components/design-system/README.md delete mode 100644 src/components/design-system/Toast/Toast.tsx delete mode 100644 src/components/design-system/index.ts delete mode 100644 src/components/task-manager/components/filter/TaskFilter.tsx delete mode 100644 src/components/task-manager/components/filter/index.ts delete mode 100644 src/components/task-manager/components/form/TaskForm.tsx delete mode 100644 src/components/task-manager/components/form/index.ts delete mode 100644 src/components/task-manager/components/index.ts delete mode 100644 src/components/task-manager/components/list/TasksList.tsx delete mode 100644 src/components/task-manager/components/list/index.ts delete mode 100644 src/components/task-manager/index.ts delete mode 100644 src/components/task-manager/store/helpers.tsx delete mode 100644 src/components/task-manager/store/index.ts delete mode 100644 src/main.tsx delete mode 100644 tsconfig.json delete mode 100644 vite.config.ts diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..386be14 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,53 @@ +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/typescript", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2021, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "plugins": [ + "react", + "react-hooks", + "@typescript-eslint", + "import", + "prettier" + ], + "rules": { + "react/react-in-jsx-scope": "off", + "prettier/prettier": "error", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }], + "import/order": [ + "error", + { + "groups": ["builtin", "external", "internal", "parent", "sibling", "index"], + "newlines-between": "always", + "alphabetize": { "order": "asc", "caseInsensitive": true } + } + ] + }, + "settings": { + "react": { + "version": "detect" + }, + "import/resolver": { + "node": { + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + } + }, + "ignorePatterns": ["dist", "node_modules", "**/*.d.ts"] +} diff --git a/.github/workflows/deploy-to-vercel.yml b/.github/workflows/deploy-to-vercel.yml new file mode 100644 index 0000000..02b382e --- /dev/null +++ b/.github/workflows/deploy-to-vercel.yml @@ -0,0 +1,62 @@ +name: Deploy to Vercel + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + run: | + echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: Setup pnpm cache + uses: actions/cache@v3 + with: + path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install + + - name: Run linting + run: pnpm lint + + - name: Run tests + run: pnpm test + + - name: Build application + if: ${{ success() }} + run: pnpm build + + - name: Deploy to Vercel + if: ${{ success() }} + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + working-directory: packages/task-manager + vercel-args: '--prod' diff --git a/.gitignore b/.gitignore index a0d218e..cb71b2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules dist -.env \ No newline at end of file +.env +coverage +**/__screenshots__ \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..c8ad74e --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100, + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf" +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..2bc6edf --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,80 @@ +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooksPlugin from 'eslint-plugin-react-hooks'; +import importPlugin from 'eslint-plugin-import'; +import prettierPlugin from 'eslint-plugin-prettier'; +import prettierConfig from 'eslint-config-prettier'; + +export default [ + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['**/*.{js,jsx,ts,tsx}'], + plugins: { + react: reactPlugin, + 'react-hooks': reactHooksPlugin, + }, + settings: { + react: { + version: 'detect', + }, + }, + rules: { + ...reactPlugin.configs.recommended.rules, + 'react/react-in-jsx-scope': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + }, + }, + { + files: ['**/*.{ts,tsx}'], + ignores: ['**/*.config.ts', '**/vitest.setup.js', '**/*.spec.ts', '**/*.spec.tsx'], + languageOptions: { + ecmaVersion: 2021, + sourceType: 'module', + parser: tseslint.parser, + parserOptions: { + project: ['./packages/*/tsconfig.json'], + ecmaFeatures: { + jsx: true, + }, + }, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + 'react': reactPlugin, + 'react-hooks': reactHooksPlugin, + 'import': importPlugin, + 'prettier': prettierPlugin, + }, + settings: { + react: { + version: 'detect', + }, + 'import/resolver': { + node: { + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + }, + }, + rules: { + 'react/react-in-jsx-scope': 'off', + 'prettier/prettier': 'error', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + 'import/order': [ + 'error', + { + groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], + 'newlines-between': 'always', + alphabetize: { order: 'asc', caseInsensitive: true }, + }, + ], + }, + }, + prettierConfig, + { + ignores: ['**/dist/**', '**/node_modules/**', '**/*.d.ts'], + }, +]; diff --git a/package.json b/package.json index d2605da..7cac707 100644 --- a/package.json +++ b/package.json @@ -1,33 +1,40 @@ { - "name": "task-manager", - "version": "0.13.0", + "name": "lateral-monorepo", + "version": "1.0.0", "private": true, + "type": "module", "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "test": "vitest run --browser.headless", - "test:watch": "vitest --browser.headless" - }, - "dependencies": { - "@supabase/supabase-js": "^2.49.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "zustand": "^5.0.3" + "dev": "pnpm --filter @lateral/task-manager dev", + "build": "pnpm --filter \"*\" build", + "build:ds": "pnpm --filter @lateral/design-system build", + "build:app": "pnpm --filter @lateral/task-manager build", + "preview": "pnpm --filter @lateral/task-manager preview", + "test": "pnpm --filter \"*\" test", + "test:watch": "pnpm --filter \"*\" test:watch", + "lint": "eslint \"packages/**/*.{ts,tsx}\" --max-warnings=0", + "lint:fix": "eslint \"packages/**/*.{ts,tsx}\" --fix", + "format": "prettier --write \"packages/**/*.{ts,tsx,json}\"" }, "devDependencies": { - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", + "@eslint/js": "^9.24.0", + "@typescript-eslint/eslint-plugin": "^8.29.1", + "@typescript-eslint/parser": "^8.29.1", "@vitejs/plugin-react": "^3.1.0", "@vitest/browser": "^3.1.1", - "autoprefixer": "^10.0.0", + "eslint": "^9.24.0", + "eslint-config-prettier": "^10.1.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", "jsdom": "^24.0.0", + "path": "^0.12.7", "playwright": "^1.51.1", "playwright-core": "^1.51.1", - "postcss": "^8.0.0", - "tailwindcss": "^3.0.0", + "prettier": "^3.5.3", "typescript": "^4.6.0", - "vite": "^4.5.13", + "typescript-eslint": "^8.29.1", + "vite": "^4.5.0", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.1.1", "vitest-browser-react": "^0.1.1", diff --git a/packages/design-system/README.md b/packages/design-system/README.md new file mode 100644 index 0000000..be7cc03 --- /dev/null +++ b/packages/design-system/README.md @@ -0,0 +1,34 @@ +# Design System + +Atomic design components that can be reused accross multiple projects. + +## Build + +To build it simply run `pnpm build` *from the root folder* which will build alls packages or `pnpm build:ds` to build this package only. + +## Test +You can run the test from the root folder using the following command: + +```bash +pnpm --filter @lateral/design-system test +``` + +You can also watch for changes: + +```bash +pnpm --filter @lateral/design-system test:watch +``` + +Alternativelly, you can also `cd` into this folder and run `pnpm test` or `pnpm test:watch`: + +```bash +cd packages/design-system +``` + +```bash +pnpm test +``` + +```bash +pnpm test:watch +``` \ No newline at end of file diff --git a/packages/design-system/package.json b/packages/design-system/package.json new file mode 100644 index 0000000..200e7a9 --- /dev/null +++ b/packages/design-system/package.json @@ -0,0 +1,23 @@ +{ + "name": "@lateral/design-system", + "version": "0.1.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --browser.headless", + "test:watch": "vitest --browser.headless" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "typescript": "^4.6.0" + } +} diff --git a/src/components/design-system/Button/Button.spec.tsx b/packages/design-system/src/Button/Button.spec.tsx similarity index 79% rename from src/components/design-system/Button/Button.spec.tsx rename to packages/design-system/src/Button/Button.spec.tsx index b118285..e9ce599 100644 --- a/src/components/design-system/Button/Button.spec.tsx +++ b/packages/design-system/src/Button/Button.spec.tsx @@ -13,16 +13,15 @@ describe('Button', () => { await expect.element(button).toHaveAttribute('type', 'button'); }); - it.each( - ['primary', 'secondary', 'danger'] as ButtonProps['variant'][] - )('renders with %s variant', async (variant) => { - const { getByRole } = render( - - ); - - const button = await getByRole('button', { name: variant }); - await expect.element(button).toBeInTheDocument(); - }); + it.each(['primary', 'secondary', 'danger'] as ButtonProps['variant'][])( + 'renders with %s variant', + async variant => { + const { getByRole } = render(); + + const button = await getByRole('button', { name: variant }); + await expect.element(button).toBeInTheDocument(); + } + ); it('renders with custom type', async () => { const { getByRole } = render(); diff --git a/src/components/design-system/Button/Button.tsx b/packages/design-system/src/Button/Button.tsx similarity index 60% rename from src/components/design-system/Button/Button.tsx rename to packages/design-system/src/Button/Button.tsx index b1768c3..2ba474f 100644 --- a/src/components/design-system/Button/Button.tsx +++ b/packages/design-system/src/Button/Button.tsx @@ -1,15 +1,15 @@ -import React from "react"; +import React from 'react'; -import { ButtonProps } from "./types"; -import { baseStyles, variantStyles } from "./styles"; +import { baseStyles, variantStyles } from './styles'; +import { ButtonProps } from './types'; export const Button: React.FC = ({ children, onClick, disabled = false, - type = "button", - variant = "primary", - className = "", + type = 'button', + variant = 'primary', + className = '', }) => ( -
    diff --git a/packages/design-system/src/ConfirmDialog/index.ts b/packages/design-system/src/ConfirmDialog/index.ts new file mode 100644 index 0000000..54459ee --- /dev/null +++ b/packages/design-system/src/ConfirmDialog/index.ts @@ -0,0 +1,2 @@ +export * from './ConfirmDialog'; +export * from './types'; diff --git a/src/components/design-system/ConfirmDialog/types.tsx b/packages/design-system/src/ConfirmDialog/types.tsx similarity index 87% rename from src/components/design-system/ConfirmDialog/types.tsx rename to packages/design-system/src/ConfirmDialog/types.tsx index 1c196df..0fa890e 100644 --- a/src/components/design-system/ConfirmDialog/types.tsx +++ b/packages/design-system/src/ConfirmDialog/types.tsx @@ -1,5 +1,3 @@ -import React from "react"; - export interface ConfirmDialogProps { isOpen: boolean; title: string; diff --git a/src/components/design-system/Input/Input.spec.tsx b/packages/design-system/src/Input/Input.spec.tsx similarity index 80% rename from src/components/design-system/Input/Input.spec.tsx rename to packages/design-system/src/Input/Input.spec.tsx index d25f770..e81c8ad 100644 --- a/src/components/design-system/Input/Input.spec.tsx +++ b/packages/design-system/src/Input/Input.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { userEvent } from '@vitest/browser/context' +import { userEvent } from '@vitest/browser/context'; import { render } from 'vitest-browser-react'; import { Input } from './Input'; import { InputProps } from './types'; @@ -29,13 +29,22 @@ describe('Input', () => { it('calls onChange handler when input changes', async () => { const StatefullInput = (props: InputProps) => { const [value, setValue] = React.useState(props.value); - return { setValue(e.target.value); props.onChange?.(e); }} />; + return ( + { + setValue(e.target.value); + props.onChange?.(e); + }} + /> + ); }; - const { getByRole } = render(); + const { getByRole } = render(); const input = await getByRole('textbox'); - + await expect(input).toHaveValue('initial value'); await expect(handleChange).not.toHaveBeenCalled(); @@ -57,7 +66,9 @@ describe('Input', () => { }); it('applies placeholder text', async () => { - const { getByPlaceholder } = render(); + const { getByPlaceholder } = render( + + ); const input = await getByPlaceholder('Enter your name'); await expect.element(input).toBeInTheDocument(); diff --git a/src/components/design-system/Input/Input.tsx b/packages/design-system/src/Input/Input.tsx similarity index 58% rename from src/components/design-system/Input/Input.tsx rename to packages/design-system/src/Input/Input.tsx index 8a298d3..3d1d09b 100644 --- a/src/components/design-system/Input/Input.tsx +++ b/packages/design-system/src/Input/Input.tsx @@ -1,14 +1,14 @@ -import React from "react"; +import React from 'react'; -import { InputProps } from "./types"; -import { baseStyles } from "./styles"; +import { baseStyles } from './styles'; +import { InputProps } from './types'; export const Input: React.FC = ({ value, onChange, - type = "text", - placeholder = "", - className = "", + type = 'text', + placeholder = '', + className = '', }) => ( { it('renders correctly', async () => { const { getByRole } = render(); - + const loadingElement = await getByRole('status'); await expect.element(loadingElement).toBeInTheDocument(); }); diff --git a/packages/design-system/src/LoadingIndicator/LoadingIndicator.tsx b/packages/design-system/src/LoadingIndicator/LoadingIndicator.tsx new file mode 100644 index 0000000..bd8d00b --- /dev/null +++ b/packages/design-system/src/LoadingIndicator/LoadingIndicator.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export const LoadingIndicator = () => ( +
    +
    +
    +); diff --git a/src/components/design-system/LoadingIndicator/index.ts b/packages/design-system/src/LoadingIndicator/index.ts similarity index 100% rename from src/components/design-system/LoadingIndicator/index.ts rename to packages/design-system/src/LoadingIndicator/index.ts diff --git a/packages/design-system/src/Toast/Toast.tsx b/packages/design-system/src/Toast/Toast.tsx new file mode 100644 index 0000000..44819f5 --- /dev/null +++ b/packages/design-system/src/Toast/Toast.tsx @@ -0,0 +1,22 @@ +import React, { useEffect } from 'react'; + +import { TOAST_AUTO_CLOSE_TIMEOUT } from './constants'; +import { colorClasses, toastClasses } from './styles'; +import { ToastProps } from './types'; + +export const Toast: React.FC = ({ message, onClose, type = 'success' }) => { + useEffect(() => { + const timer = setTimeout(onClose, TOAST_AUTO_CLOSE_TIMEOUT); + + return () => clearTimeout(timer); + }, [onClose]); + + return ( +
    + {message} + +
    + ); +}; diff --git a/src/components/design-system/Toast/ToastProvider.tsx b/packages/design-system/src/Toast/ToastProvider.tsx similarity index 77% rename from src/components/design-system/Toast/ToastProvider.tsx rename to packages/design-system/src/Toast/ToastProvider.tsx index 043a19d..b0f8a3a 100644 --- a/src/components/design-system/Toast/ToastProvider.tsx +++ b/packages/design-system/src/Toast/ToastProvider.tsx @@ -1,6 +1,7 @@ import React, { createContext, useState, useContext } from 'react'; -import type { ToastContextProps, ToastType } from './types'; + import { Toast } from './Toast'; +import type { ToastContextProps, ToastType } from './types'; const ToastContext = createContext(undefined); @@ -17,11 +18,11 @@ export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ childre const showToast = (message: string, type: ToastType = 'success') => { const id = `toast-${Date.now()}`; - setToasts((prevToasts) => [...prevToasts, { id, message, type }]); + setToasts(prevToasts => [...prevToasts, { id, message, type }]); }; const closeToast = (id: string) => { - setToasts((prevToasts) => prevToasts.filter((toast) => toast.id !== id)); + setToasts(prevToasts => prevToasts.filter(toast => toast.id !== id)); }; return ( @@ -29,12 +30,7 @@ export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ childre {children}
    {toasts.map(({ id, message, type }) => ( - closeToast(id)} - /> + closeToast(id)} /> ))}
    diff --git a/src/components/design-system/Toast/constants.ts b/packages/design-system/src/Toast/constants.ts similarity index 100% rename from src/components/design-system/Toast/constants.ts rename to packages/design-system/src/Toast/constants.ts diff --git a/src/components/design-system/Toast/index.ts b/packages/design-system/src/Toast/index.ts similarity index 74% rename from src/components/design-system/Toast/index.ts rename to packages/design-system/src/Toast/index.ts index 0e2c6a2..19385dc 100644 --- a/src/components/design-system/Toast/index.ts +++ b/packages/design-system/src/Toast/index.ts @@ -1,3 +1,4 @@ export * from './Toast'; export * from './ToastProvider'; export * from './types'; +export * from './constants'; diff --git a/src/components/design-system/Toast/styles.tsx b/packages/design-system/src/Toast/styles.tsx similarity index 60% rename from src/components/design-system/Toast/styles.tsx rename to packages/design-system/src/Toast/styles.tsx index 1147d7b..20e2a2b 100644 --- a/src/components/design-system/Toast/styles.tsx +++ b/packages/design-system/src/Toast/styles.tsx @@ -1,9 +1,9 @@ -export const toastClasses = +export const toastClasses = 'fixed bottom-4 right-4 text-white px-6 py-3 rounded shadow-lg flex items-center justify-between min-w-[250px] transform transition-all duration-500 ease-in-out'; export const colorClasses = { - success: "bg-green-500", - error: "bg-red-500", - warning: "bg-yellow-500", - info: "bg-blue-500", + success: 'bg-green-500', + error: 'bg-red-500', + warning: 'bg-yellow-500', + info: 'bg-blue-500', }; diff --git a/src/components/design-system/Toast/types.tsx b/packages/design-system/src/Toast/types.tsx similarity index 75% rename from src/components/design-system/Toast/types.tsx rename to packages/design-system/src/Toast/types.tsx index 2f1fb2e..793d863 100644 --- a/src/components/design-system/Toast/types.tsx +++ b/packages/design-system/src/Toast/types.tsx @@ -1,4 +1,4 @@ -export type ToastType = "success" | "error" | "warning" | "info"; +export type ToastType = 'success' | 'error' | 'warning' | 'info'; export interface ToastProps { message: string; diff --git a/packages/design-system/src/index.ts b/packages/design-system/src/index.ts new file mode 100644 index 0000000..d5f99e3 --- /dev/null +++ b/packages/design-system/src/index.ts @@ -0,0 +1,5 @@ +export * from './Button'; +export * from './Input'; +export * from './LoadingIndicator'; +export * from './ConfirmDialog'; +export * from './Toast'; diff --git a/packages/design-system/tsconfig.json b/packages/design-system/tsconfig.json new file mode 100644 index 0000000..2b7e244 --- /dev/null +++ b/packages/design-system/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": false, + "jsx": "react-jsx", + "strict": true, + "declaration": true, + "outDir": "dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"], + "exclude": ["**/*.spec.ts", "**/*.spec.tsx"] +} diff --git a/packages/task-manager/README.md b/packages/task-manager/README.md new file mode 100644 index 0000000..137c91a --- /dev/null +++ b/packages/task-manager/README.md @@ -0,0 +1,39 @@ +# Task Manager + +A simple task manager vite app + +## Run + +Run the preview app with a production build using the `pnpm preview` command *from the root folder*. + +For development you can use `pnpm dev`. + +## Build + +To build it simply run `pnpm build` *from the root folder* which will build alls packages or `pnpm build:app` to build this app only. + +## Test +You can run the test from the root folder using the following command: + +```bash +pnpm --filter @lateral/task-manager test +``` + +You can also watch for changes: + +```bash +pnpm --filter @lateral/task-manager test:watch +``` + +Alternativelly, you can also `cd` into this folder and run `pnpm test` or `pnpm test:watch`: + +```bash +cd packages/task-manager +``` + +```bash +pnpm test +``` + +```bash +pnpm test:watch \ No newline at end of file diff --git a/index.html b/packages/task-manager/index.html similarity index 100% rename from index.html rename to packages/task-manager/index.html diff --git a/packages/task-manager/package.json b/packages/task-manager/package.json new file mode 100644 index 0000000..dbe0667 --- /dev/null +++ b/packages/task-manager/package.json @@ -0,0 +1,37 @@ +{ + "name": "@lateral/task-manager", + "version": "0.13.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "vitest run --browser.headless", + "test:watch": "vitest --browser.headless" + }, + "dependencies": { + "@lateral/design-system": "workspace:*", + "@supabase/supabase-js": "^2.49.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@vitejs/plugin-react": "^3.1.0", + "@vitest/browser": "^3.1.1", + "autoprefixer": "^10.0.0", + "jsdom": "^24.0.0", + "playwright": "^1.51.1", + "playwright-core": "^1.51.1", + "postcss": "^8.0.0", + "tailwindcss": "^3.0.0", + "typescript": "^4.6.0", + "vite": "^4.5.13", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.1.1", + "vitest-browser-react": "^0.1.1", + "vitest-dom": "^0.1.1" + } +} diff --git a/postcss.config.js b/packages/task-manager/postcss.config.js similarity index 100% rename from postcss.config.js rename to packages/task-manager/postcss.config.js diff --git a/src/App.tsx b/packages/task-manager/src/App.tsx similarity index 59% rename from src/App.tsx rename to packages/task-manager/src/App.tsx index 3029f92..c63f5bc 100644 --- a/src/App.tsx +++ b/packages/task-manager/src/App.tsx @@ -1,7 +1,7 @@ -import React from "react"; +import { ToastProvider } from '@lateral/design-system'; +import React from 'react'; -import { TaskManager } from "./components/task-manager"; -import { ToastProvider } from "./components/design-system"; +import { TaskManager } from './components/manager'; export function App() { return ( @@ -10,8 +10,9 @@ export function App() {

    Task Manager

    - - +
    + +
    ); diff --git a/packages/task-manager/src/components/filter/TaskFilter.spec.tsx b/packages/task-manager/src/components/filter/TaskFilter.spec.tsx new file mode 100644 index 0000000..a81cb35 --- /dev/null +++ b/packages/task-manager/src/components/filter/TaskFilter.spec.tsx @@ -0,0 +1,38 @@ +import { describe, it, expect, vi } from 'vitest'; +import { render } from 'vitest-browser-react'; + +import { TaskFilter } from './TaskFilter'; + +describe('TaskFilter', () => { + it('renders all filter buttons', async () => { + const { getByRole } = render( {}} />); + + await expect.element(getByRole('button', { name: /all/i })).toBeInTheDocument(); + await expect.element(getByRole('button', { name: /completed/i })).toBeInTheDocument(); + await expect.element(getByRole('button', { name: /pending/i })).toBeInTheDocument(); + }); + + it('applies primary variant to selected filter', async () => { + const { getByRole } = render( + {}} /> + ); + + const allButton = getByRole('button', { name: /all/i }); + const completedButton = getByRole('button', { name: /completed/i }); + + await expect(completedButton).toHaveClass('bg-blue-500'); + await expect(allButton).not.toHaveClass('bg-blue-500'); + }); + + it('calls onFilterChange with correct filter value when clicked', async () => { + const onFilterChange = vi.fn(); + const { getByRole } = render( + + ); + + const pendingButton = await getByRole('button', { name: /pending/i }); + await pendingButton.click(); + + expect(onFilterChange).toHaveBeenCalledWith('pending'); + }); +}); diff --git a/packages/task-manager/src/components/filter/TaskFilter.tsx b/packages/task-manager/src/components/filter/TaskFilter.tsx new file mode 100644 index 0000000..d55943d --- /dev/null +++ b/packages/task-manager/src/components/filter/TaskFilter.tsx @@ -0,0 +1,32 @@ +import { Button } from '@lateral/design-system'; +import React from 'react'; + +import type { TaskFilterProps } from './types'; + +export const TaskFilter: React.FC = ({ currentFilter, onFilterChange }) => { + return ( +
    + + + +
    + ); +}; diff --git a/packages/task-manager/src/components/filter/index.ts b/packages/task-manager/src/components/filter/index.ts new file mode 100644 index 0000000..87d0dbd --- /dev/null +++ b/packages/task-manager/src/components/filter/index.ts @@ -0,0 +1 @@ +export * from './TaskFilter'; diff --git a/src/components/task-manager/components/filter/types.tsx b/packages/task-manager/src/components/filter/types.tsx similarity index 68% rename from src/components/task-manager/components/filter/types.tsx rename to packages/task-manager/src/components/filter/types.tsx index f7fd596..7a6b692 100644 --- a/src/components/task-manager/components/filter/types.tsx +++ b/packages/task-manager/src/components/filter/types.tsx @@ -1,4 +1,4 @@ -import type { FilterType } from "../../store/models"; +import type { FilterType } from '../../store/models'; export interface TaskFilterProps { currentFilter: FilterType; diff --git a/packages/task-manager/src/components/form/TaskForm.spec.tsx b/packages/task-manager/src/components/form/TaskForm.spec.tsx new file mode 100644 index 0000000..65c75a7 --- /dev/null +++ b/packages/task-manager/src/components/form/TaskForm.spec.tsx @@ -0,0 +1,71 @@ +import { userEvent } from '@vitest/browser/context'; +import { describe, it, expect, vi } from 'vitest'; +import { render } from 'vitest-browser-react'; + +import { TaskForm } from './TaskForm'; + +describe('TaskForm', () => { + it('renders form with input and button', async () => { + const { getByRole, getByPlaceholder } = render( {}} />); + + await expect.element(getByPlaceholder('New task...')).toBeInTheDocument(); + await expect.element(getByRole('button', { name: /add/i })).toBeInTheDocument(); + }); + + it('button is disabled when input is empty', async () => { + const { getByRole } = render( {}} />); + + const button = await getByRole('button', { name: /add/i }); + await expect.element(button).toBeDisabled(); + }); + + it('button is enabled when input has value', async () => { + const { getByRole, getByPlaceholder } = render( {}} />); + + const input = await getByPlaceholder('New task...'); + const button = await getByRole('button', { name: /add/i }); + + await userEvent.type(input, 'New task'); + + await expect.element(button).not.toBeDisabled(); + }); + + it('calls onAddTask with input value on submit', async () => { + const onAddTask = vi.fn(); + const { getByRole, getByPlaceholder } = render(); + + const input = await getByPlaceholder('New task...'); + const button = await getByRole('button', { name: /add/i }); + + await userEvent.type(input, 'Test task'); + await button.click(); + + expect(onAddTask).toHaveBeenCalledWith('Test task'); + expect(input).toHaveValue(''); + }); + + it('does not call onAddTask when input is empty', async () => { + const onAddTask = vi.fn(); + const { container, getByRole } = render(); + + const addButton = await getByRole('button', { name: /add/i }); + expect(addButton).toBeDisabled(); + + await container.querySelector('form')?.dispatchEvent(new Event('submit', { bubbles: true })); + + expect(onAddTask).not.toHaveBeenCalled(); + }); + + it('trims whitespace from input value', async () => { + const onAddTask = vi.fn(); + const { getByRole, getByPlaceholder } = render(); + + const input = await getByPlaceholder('New task...'); + const button = await getByRole('button', { name: /add/i }); + + await userEvent.type(input, ' Trim me '); + await button.click(); + + expect(onAddTask).toHaveBeenCalledWith('Trim me'); + }); +}); diff --git a/packages/task-manager/src/components/form/TaskForm.tsx b/packages/task-manager/src/components/form/TaskForm.tsx new file mode 100644 index 0000000..2f9a0e4 --- /dev/null +++ b/packages/task-manager/src/components/form/TaskForm.tsx @@ -0,0 +1,41 @@ +import { Button, Input } from '@lateral/design-system'; +import React, { useState, useCallback } from 'react'; + +import type { TaskFormProps } from './types'; + +export const TaskForm: React.FC = ({ onAddTask }) => { + const [newTask, setNewTask] = useState(''); + const sanitizedTask = newTask.trim(); + + const handleSubmit = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + if (!sanitizedTask) return; + + onAddTask(sanitizedTask); + setNewTask(''); + }, + [sanitizedTask, onAddTask] + ); + + return ( + + setNewTask(e.target.value)} + /> + + + ); +}; diff --git a/packages/task-manager/src/components/form/index.ts b/packages/task-manager/src/components/form/index.ts new file mode 100644 index 0000000..c46c0dc --- /dev/null +++ b/packages/task-manager/src/components/form/index.ts @@ -0,0 +1 @@ +export * from './TaskForm'; diff --git a/src/components/task-manager/components/form/types.tsx b/packages/task-manager/src/components/form/types.tsx similarity index 100% rename from src/components/task-manager/components/form/types.tsx rename to packages/task-manager/src/components/form/types.tsx diff --git a/packages/task-manager/src/components/index.ts b/packages/task-manager/src/components/index.ts new file mode 100644 index 0000000..be41d6e --- /dev/null +++ b/packages/task-manager/src/components/index.ts @@ -0,0 +1,5 @@ +export * from './form/TaskForm'; +export * from './filter/TaskFilter'; +export * from './item/TaskItem'; +export * from './list/TasksList'; +export * from './manager/TaskManager'; diff --git a/packages/task-manager/src/components/item/TaskItem.spec.tsx b/packages/task-manager/src/components/item/TaskItem.spec.tsx new file mode 100644 index 0000000..be1d9e1 --- /dev/null +++ b/packages/task-manager/src/components/item/TaskItem.spec.tsx @@ -0,0 +1,111 @@ +import type { ButtonProps, ConfirmDialogProps } from '@lateral/design-system'; +import { describe, it, expect, vi } from 'vitest'; +import { render } from 'vitest-browser-react'; + +import { TaskItem } from './TaskItem'; + +vi.mock('@lateral/design-system', () => ({ + Button: ({ children, onClick, variant, className }: ButtonProps) => ( + + ), + ConfirmDialog: ({ isOpen, onConfirm, onCancel, title, message }: ConfirmDialogProps) => + isOpen ? ( +
    +
    {title}
    +
    {message}
    + + +
    + ) : null, + useToast: () => ({ + showToast: vi.fn(), + }), +})); + +describe('TaskItem', () => { + const mockTask = { + id: '1', + title: 'Test Task', + completed: false, + }; + + it('renders task item with correct title', async () => { + const { container } = render( + {}} onToggle={() => {}} /> + ); + + await expect.element(container).toHaveTextContent('Test Task'); + }); + + it('applies strikethrough style when task is completed', async () => { + const completedTask = { ...mockTask, completed: true }; + const { container } = render( + {}} onToggle={() => {}} /> + ); + + const taskText = await container.querySelector('.text-green-500.line-through'); + await expect.element(taskText).toHaveTextContent('Test Task'); + }); + + it('calls onToggle when task text is clicked', async () => { + const onToggle = vi.fn(); + const { getByText } = render( + {}} onToggle={onToggle} /> + ); + + const taskText = await getByText('Test Task'); + await taskText.click(); + + expect(onToggle).toHaveBeenCalledWith('1'); + }); + + it('shows confirm dialog when delete button is clicked', async () => { + const { getByRole } = render( + {}} onToggle={() => {}} /> + ); + + const dialog = await getByRole('dialog'); + await expect.element(dialog).not.toBeInTheDocument(); + + const deleteButton = await getByRole('button', { name: /delete/i }); + await deleteButton.click(); + + await expect.element(getByRole('dialog')).toBeInTheDocument(); + await expect + .element(getByRole('dialog')) + .toHaveTextContent('Are you sure you want to delete "Test Task"?'); + }); + + it('calls onDelete when confirm button is clicked', async () => { + const onDelete = vi.fn(); + const { getByRole } = render( + {}} /> + ); + + const deleteButton = await getByRole('button', { name: /delete/i }); + await deleteButton.click(); + + const confirmButton = await getByRole('button', { name: /confirm/i }); + await confirmButton.click(); + + expect(onDelete).toHaveBeenCalledWith('1'); + }); + + it('hides confirm dialog when cancel button is clicked', async () => { + const { getByRole } = render( + {}} onToggle={() => {}} /> + ); + + const deleteButton = await getByRole('button', { name: /delete/i }); + await deleteButton.click(); + + await expect.element(getByRole('dialog')).toBeInTheDocument(); + + const cancelButton = await getByRole('button', { name: /cancel/i }); + await cancelButton.click(); + + await expect.element(getByRole('dialog')).not.toBeInTheDocument(); + }); +}); diff --git a/src/components/task-manager/components/item/TaskItem.tsx b/packages/task-manager/src/components/item/TaskItem.tsx similarity index 81% rename from src/components/task-manager/components/item/TaskItem.tsx rename to packages/task-manager/src/components/item/TaskItem.tsx index 59d65d9..db6e310 100644 --- a/src/components/task-manager/components/item/TaskItem.tsx +++ b/packages/task-manager/src/components/item/TaskItem.tsx @@ -1,28 +1,29 @@ -import React, { useState } from "react"; -import type { TaskItemProps } from "./types"; -import { Button, ConfirmDialog, useToast } from "../../../design-system"; +import { Button, ConfirmDialog, useToast } from '@lateral/design-system'; +import React, { useState } from 'react'; + +import type { TaskItemProps } from './types'; export const TaskItem: React.FC = ({ task, onDelete, onToggle }) => { const [showConfirmDialog, setShowConfirmDialog] = useState(false); const { showToast } = useToast(); const toggleConfirmDialog = () => { - setShowConfirmDialog((open) => !open); + setShowConfirmDialog(open => !open); }; - + const handleConfirmDelete = () => { onDelete(task.id); toggleConfirmDialog(); showToast(`Task "${task.title}" deleted successfully`); }; - + return (
  • onToggle(task.id)} className={`cursor-pointer text-sm sm:text-base truncate ${ - task.completed ? "line-through text-green-500" : "text-black" + task.completed ? 'line-through text-green-500' : 'text-black' }`} > {task.title} @@ -36,7 +37,7 @@ export const TaskItem: React.FC = ({ task, onDelete, onToggle }) > Delete - + ({ + TaskItem: ({ task }: { task: { id: string; title: string } }) => ( +
    + {task.title} +
    + ), +})); + +describe('TasksList', () => { + const mockTasks = [ + { id: '1', title: 'Task 1', completed: false }, + { id: '2', title: 'Task 2', completed: true }, + ]; + + it('renders a list of task items', async () => { + const { container } = render( + {}} onToggle={() => {}} /> + ); + + const taskItems = await container.querySelectorAll('[data-testid="task-item"]'); + + expect(taskItems.length).toBe(2); + await expect.element(taskItems[0]).toHaveTextContent('Task 1'); + await expect.element(taskItems[1]).toHaveTextContent('Task 2'); + }); + + it('renders "No tasks found" message when tasks array is empty', async () => { + const { container } = render( {}} onToggle={() => {}} />); + + await expect.element(container).toHaveTextContent('No tasks found'); + const taskItems = await container.querySelectorAll('[data-testid="task-item"]'); + expect(taskItems.length).toBe(0); + }); + + it('passes correct props to TaskItem components', async () => { + const onDelete = vi.fn(); + const onToggle = vi.fn(); + const { container } = render( + + ); + + const taskItems = await container.querySelectorAll('[data-testid="task-item"]'); + expect(taskItems[0].getAttribute('data-id')).toBe('1'); + expect(taskItems[1].getAttribute('data-id')).toBe('2'); + }); +}); diff --git a/packages/task-manager/src/components/list/TasksList.tsx b/packages/task-manager/src/components/list/TasksList.tsx new file mode 100644 index 0000000..1f6cc1c --- /dev/null +++ b/packages/task-manager/src/components/list/TasksList.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import { TaskItem } from '../item/TaskItem'; + +import type { TasksListProps } from './types'; + +export const TasksList: React.FC = ({ tasks, onDelete, onToggle }) => ( +
    + {tasks.length > 0 ? ( +
      + {tasks.map(task => ( + + ))} +
    + ) : ( +

    No tasks found

    + )} +
    +); diff --git a/packages/task-manager/src/components/list/index.ts b/packages/task-manager/src/components/list/index.ts new file mode 100644 index 0000000..29c48ba --- /dev/null +++ b/packages/task-manager/src/components/list/index.ts @@ -0,0 +1,2 @@ +export * from './TasksList'; +export * from './types'; diff --git a/src/components/task-manager/components/list/types.ts b/packages/task-manager/src/components/list/types.ts similarity index 71% rename from src/components/task-manager/components/list/types.ts rename to packages/task-manager/src/components/list/types.ts index 5251b75..a271e1e 100644 --- a/src/components/task-manager/components/list/types.ts +++ b/packages/task-manager/src/components/list/types.ts @@ -1,4 +1,4 @@ -import type { Task } from "../../store/models"; +import type { Task } from '../../store/models'; export interface TasksListProps { tasks: Task[]; diff --git a/packages/task-manager/src/components/manager/TaskManager.spec.tsx b/packages/task-manager/src/components/manager/TaskManager.spec.tsx new file mode 100644 index 0000000..d55783e --- /dev/null +++ b/packages/task-manager/src/components/manager/TaskManager.spec.tsx @@ -0,0 +1,113 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render } from 'vitest-browser-react'; + +import { TaskFilterProps } from '../filter/types'; +import { TaskFormProps } from '../form/types'; +import { TasksListProps } from '../list'; + +import { TaskManager } from './TaskManager'; + +const mockFilteredTasks = [ + { id: '1', title: 'Task 1', completed: false }, + { id: '2', title: 'Task 2', completed: true }, +]; + +const mockStore = { + filteredTasks: mockFilteredTasks, + filter: 'all', + addTask: vi.fn(), + deleteTask: vi.fn(), + toggleTask: vi.fn(), + setFilter: vi.fn(), + fetchTasks: vi.fn(), + isLoading: false, +}; + +vi.mock('../../store', () => ({ + useTasksStore: () => mockStore, +})); + +vi.mock('../filter', () => ({ + TaskFilter: ({ currentFilter, onFilterChange }: TaskFilterProps) => ( +
    + + + +
    + ), +})); + +vi.mock('../form', () => ({ + TaskForm: ({ onAddTask }: TaskFormProps) => ( +
    { + e.preventDefault(); + onAddTask('New Task'); + }} + > + +
    + ), +})); + +vi.mock('../list', () => ({ + TasksList: ({ tasks, onDelete, onToggle }: TasksListProps) => ( + <> +

    Tasks List

    +
    + {tasks.map(task => ( +
    + onToggle(task.id)}>{task.title} + +
    + ))} +
    + + ), +})); + +vi.mock('@lateral/design-system', () => ({ + LoadingIndicator: () =>
    Loading...
    , +})); + +describe('TaskManager', () => { + beforeEach(vi.clearAllMocks); + + it('renders all child components', async () => { + const { getByTestId } = render(); + + await expect.element(getByTestId('task-form')).toBeInTheDocument(); + await expect.element(getByTestId('task-filter')).toBeInTheDocument(); + await expect.element(getByTestId('tasks-list')).toBeInTheDocument(); + }); + + it('calls fetchTasks on mount', async () => { + render(); + expect(mockStore.fetchTasks).toHaveBeenCalledTimes(1); + }); + + it('passes correct props to TaskFilter', async () => { + const { getByTestId } = render(); + + const filter = await getByTestId('task-filter'); + expect(filter).toHaveAttribute('data-filter', 'all'); + }); + + it('passes tasks to TasksList', async () => { + const { getByTestId } = render(); + + const tasksList = await getByTestId('tasks-list'); + expect(tasksList).toHaveAttribute('data-count', '2'); + }); + + it('shows loading indicator when isLoading is true', async () => { + mockStore.isLoading = true; + const { getByText } = render(); + + expect(getByText('Loading...')).toBeInTheDocument(); + // expect(getByText('Tasks List')).not.toBeInTheDocument(); + + mockStore.isLoading = false; + }); +}); diff --git a/src/components/task-manager/components/manager/TaskManager.tsx b/packages/task-manager/src/components/manager/TaskManager.tsx similarity index 54% rename from src/components/task-manager/components/manager/TaskManager.tsx rename to packages/task-manager/src/components/manager/TaskManager.tsx index f28d798..7274bb8 100644 --- a/src/components/task-manager/components/manager/TaskManager.tsx +++ b/packages/task-manager/src/components/manager/TaskManager.tsx @@ -1,8 +1,10 @@ -import React, { useEffect } from "react"; +import { LoadingIndicator } from '@lateral/design-system'; +import React, { useEffect } from 'react'; -import { useTasksStore } from "../../store"; -import { TaskFilter, TaskForm, TasksList } from ".."; -import { LoadingIndicator } from "../../../design-system"; +import { useTasksStore } from '../../store'; +import { TaskFilter } from '../filter'; +import { TaskForm } from '../form'; +import { TasksList } from '../list'; export const TaskManager: React.FC = () => { const { @@ -13,31 +15,24 @@ export const TaskManager: React.FC = () => { toggleTask, setFilter, fetchTasks, - isLoading + isLoading, } = useTasksStore(); useEffect(() => { fetchTasks(); - }, [fetchTasks]); + }, [fetchTasks]); return (
    - +
    {isLoading ? ( ) : ( - + )}
    ); diff --git a/packages/task-manager/src/components/manager/index.ts b/packages/task-manager/src/components/manager/index.ts new file mode 100644 index 0000000..c08b640 --- /dev/null +++ b/packages/task-manager/src/components/manager/index.ts @@ -0,0 +1 @@ +export * from './TaskManager'; diff --git a/src/index.css b/packages/task-manager/src/index.css similarity index 64% rename from src/index.css rename to packages/task-manager/src/index.css index bd6213e..b5c61c9 100644 --- a/src/index.css +++ b/packages/task-manager/src/index.css @@ -1,3 +1,3 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; diff --git a/src/lib/supabase.ts b/packages/task-manager/src/lib/supabase.ts similarity index 100% rename from src/lib/supabase.ts rename to packages/task-manager/src/lib/supabase.ts diff --git a/packages/task-manager/src/main.tsx b/packages/task-manager/src/main.tsx new file mode 100644 index 0000000..3839ab8 --- /dev/null +++ b/packages/task-manager/src/main.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import { App } from './App'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/packages/task-manager/src/store/helpers.tsx b/packages/task-manager/src/store/helpers.tsx new file mode 100644 index 0000000..b0ea233 --- /dev/null +++ b/packages/task-manager/src/store/helpers.tsx @@ -0,0 +1,9 @@ +import { Task, FilterType } from './models'; + +export const getFilteredTasks = (tasks: Task[], filter: FilterType): Task[] => { + return tasks.filter(task => { + if (filter === 'completed') return task.completed === true; + if (filter === 'pending') return task.completed === false; + return true; + }); +}; diff --git a/packages/task-manager/src/store/index.ts b/packages/task-manager/src/store/index.ts new file mode 100644 index 0000000..2a3030f --- /dev/null +++ b/packages/task-manager/src/store/index.ts @@ -0,0 +1,4 @@ +export * from './models'; +export * from './types'; +export * from './helpers'; +export * from './taskStore'; diff --git a/src/components/task-manager/store/models.ts b/packages/task-manager/src/store/models.ts similarity index 100% rename from src/components/task-manager/store/models.ts rename to packages/task-manager/src/store/models.ts diff --git a/src/components/task-manager/store/service.ts b/packages/task-manager/src/store/service.ts similarity index 80% rename from src/components/task-manager/store/service.ts rename to packages/task-manager/src/store/service.ts index 345aa73..ca122da 100644 --- a/src/components/task-manager/store/service.ts +++ b/packages/task-manager/src/store/service.ts @@ -1,4 +1,5 @@ -import { supabase } from '../../../lib/supabase'; +import { supabase } from '../lib/supabase'; + import type { Task } from './models'; export async function fetchTasks(): Promise { @@ -16,11 +17,7 @@ export async function fetchTasks(): Promise { } export async function addTask(task: Omit): Promise { - const { data, error } = await supabase - .from('tasks') - .insert([task]) - .select() - .single(); + const { data, error } = await supabase.from('tasks').insert([task]).select().single(); if (error) { console.error('Error adding task:', error); @@ -47,10 +44,7 @@ export async function updateTask(task: Task): Promise { } export async function deleteTask(id: string): Promise { - const { error } = await supabase - .from('tasks') - .delete() - .eq('id', id); + const { error } = await supabase.from('tasks').delete().eq('id', id); if (error) { console.error('Error deleting task:', error); diff --git a/src/components/task-manager/store/taskStore.ts b/packages/task-manager/src/store/taskStore.ts similarity index 82% rename from src/components/task-manager/store/taskStore.ts rename to packages/task-manager/src/store/taskStore.ts index bb75ea2..5c34097 100644 --- a/src/components/task-manager/store/taskStore.ts +++ b/packages/task-manager/src/store/taskStore.ts @@ -1,8 +1,9 @@ import { create } from 'zustand'; -import type { TasksState } from './types'; -import type { FilterType, Task } from './models'; + import { getFilteredTasks } from './helpers'; +import type { FilterType, Task } from './models'; import { fetchTasks, addTask, updateTask, deleteTask } from './service'; +import type { TasksState } from './types'; const initialTasks: Task[] = []; @@ -20,7 +21,7 @@ export const useTasksStore = create((set, get) => ({ set(state => ({ tasks, filteredTasks: getFilteredTasks(tasks, state.filter), - isLoading: false + isLoading: false, })); }, @@ -34,14 +35,11 @@ export const useTasksStore = create((set, get) => ({ const tempId = `temp-${Date.now()}`; set(state => { - const updatedTasks = [ - { ...newTaskData, id: tempId }, - ...state.tasks - ]; + const updatedTasks = [{ ...newTaskData, id: tempId }, ...state.tasks]; return { tasks: updatedTasks, - filteredTasks: getFilteredTasks(updatedTasks, state.filter) + filteredTasks: getFilteredTasks(updatedTasks, state.filter), }; }); @@ -49,12 +47,10 @@ export const useTasksStore = create((set, get) => ({ if (persistedTask) { set(state => { - const updatedTasks = state.tasks.map(task => - task.id === tempId ? persistedTask : task - ); + const updatedTasks = state.tasks.map(task => (task.id === tempId ? persistedTask : task)); return { tasks: updatedTasks, - filteredTasks: getFilteredTasks(updatedTasks, state.filter) + filteredTasks: getFilteredTasks(updatedTasks, state.filter), }; }); } @@ -69,7 +65,7 @@ export const useTasksStore = create((set, get) => ({ return { tasks: updatedTasks, - filteredTasks: getFilteredTasks(updatedTasks, state.filter) + filteredTasks: getFilteredTasks(updatedTasks, state.filter), }; }); @@ -82,7 +78,7 @@ export const useTasksStore = create((set, get) => ({ return { tasks: updatedTasks, - filteredTasks: getFilteredTasks(updatedTasks, state.filter) + filteredTasks: getFilteredTasks(updatedTasks, state.filter), }; }); } @@ -95,12 +91,10 @@ export const useTasksStore = create((set, get) => ({ const updatedTask = { ...taskToUpdate, completed: !taskToUpdate.completed }; set(state => { - const updatedTasks = state.tasks.map(task => - task.id === id ? updatedTask : task - ); + const updatedTasks = state.tasks.map(task => (task.id === id ? updatedTask : task)); return { tasks: updatedTasks, - filteredTasks: getFilteredTasks(updatedTasks, state.filter) + filteredTasks: getFilteredTasks(updatedTasks, state.filter), }; }); @@ -108,12 +102,10 @@ export const useTasksStore = create((set, get) => ({ if (!result) { set(state => { - const revertedTasks = state.tasks.map(task => - task.id === id ? taskToUpdate : task - ); + const revertedTasks = state.tasks.map(task => (task.id === id ? taskToUpdate : task)); return { tasks: revertedTasks, - filteredTasks: getFilteredTasks(revertedTasks, state.filter) + filteredTasks: getFilteredTasks(revertedTasks, state.filter), }; }); } @@ -122,7 +114,7 @@ export const useTasksStore = create((set, get) => ({ setFilter: (filter: FilterType) => { set(state => ({ filter, - filteredTasks: getFilteredTasks(state.tasks, filter) + filteredTasks: getFilteredTasks(state.tasks, filter), })); - } + }, })); diff --git a/src/components/task-manager/store/types.ts b/packages/task-manager/src/store/types.ts similarity index 100% rename from src/components/task-manager/store/types.ts rename to packages/task-manager/src/store/types.ts diff --git a/tailwind.config.js b/packages/task-manager/tailwind.config.js similarity index 51% rename from tailwind.config.js rename to packages/task-manager/tailwind.config.js index ffb9a83..3bd1f3e 100644 --- a/tailwind.config.js +++ b/packages/task-manager/tailwind.config.js @@ -1,6 +1,10 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ["./index.html", "./src/**/*.{ts,tsx}"], + content: [ + "./index.html", + "./src/**/*.{ts,tsx}", + "../design-system/src/**/*.{ts,tsx}" + ], theme: { extend: {}, }, diff --git a/packages/task-manager/tsconfig.json b/packages/task-manager/tsconfig.json new file mode 100644 index 0000000..e0eaab0 --- /dev/null +++ b/packages/task-manager/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["esnext", "dom"], + "module": "ESNext", + "moduleResolution": "node", + "jsx": "react-jsx", + "types": ["@vitest/browser/providers/playwright"], + "strict": true, + "declaration": true, + "noEmit": true, + "esModuleInterop": true, + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"], + "@task-manager/design-system": ["../design-system/src"] + } + }, + "include": ["src"] +} diff --git a/packages/task-manager/vite.config.ts b/packages/task-manager/vite.config.ts new file mode 100644 index 0000000..fac4f26 --- /dev/null +++ b/packages/task-manager/vite.config.ts @@ -0,0 +1,14 @@ +import { defineProject } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +// https://vitejs.dev/config/ +export default defineProject({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + '@lateral/design-system': path.resolve(__dirname, '../design-system/src'), + }, + }, +}); diff --git a/packages/task-manager/vitest.config.ts b/packages/task-manager/vitest.config.ts new file mode 100644 index 0000000..beaeb44 --- /dev/null +++ b/packages/task-manager/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineProject } from 'vitest/config'; + +export default defineProject({ + test: { + browser: { + enabled: true, + provider: 'playwright', + // https://vitest.dev/guide/browser/playwright + instances: [ + { + browser: 'chromium', + }, + ], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7631486..f4682df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,7 +7,101 @@ settings: importers: .: + devDependencies: + '@eslint/js': + specifier: ^9.24.0 + version: 9.24.0 + '@typescript-eslint/eslint-plugin': + specifier: ^8.29.1 + version: 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5))(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + '@typescript-eslint/parser': + specifier: ^8.29.1 + version: 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + '@vitejs/plugin-react': + specifier: ^3.1.0 + version: 3.1.0(vite@4.5.13(@types/node@22.14.0)) + '@vitest/browser': + specifier: ^3.1.1 + version: 3.1.1(playwright@1.51.1)(vite@4.5.13(@types/node@22.14.0))(vitest@3.1.1) + eslint: + specifier: ^9.24.0 + version: 9.24.0(jiti@1.21.7) + eslint-config-prettier: + specifier: ^10.1.2 + version: 10.1.2(eslint@9.24.0(jiti@1.21.7)) + eslint-plugin-import: + specifier: ^2.31.0 + version: 2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5))(eslint@9.24.0(jiti@1.21.7)) + eslint-plugin-prettier: + specifier: ^5.2.6 + version: 5.2.6(eslint-config-prettier@10.1.2(eslint@9.24.0(jiti@1.21.7)))(eslint@9.24.0(jiti@1.21.7))(prettier@3.5.3) + eslint-plugin-react: + specifier: ^7.37.5 + version: 7.37.5(eslint@9.24.0(jiti@1.21.7)) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.24.0(jiti@1.21.7)) + jsdom: + specifier: ^24.0.0 + version: 24.1.3 + path: + specifier: ^0.12.7 + version: 0.12.7 + playwright: + specifier: ^1.51.1 + version: 1.51.1 + playwright-core: + specifier: ^1.51.1 + version: 1.51.1 + prettier: + specifier: ^3.5.3 + version: 3.5.3 + typescript: + specifier: ^4.6.0 + version: 4.9.5 + typescript-eslint: + specifier: ^8.29.1 + version: 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + vite: + specifier: ^4.5.0 + version: 4.5.13(@types/node@22.14.0) + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@4.9.5)(vite@4.5.13(@types/node@22.14.0)) + vitest: + specifier: ^3.1.1 + version: 3.1.1(@types/node@22.14.0)(@vitest/browser@3.1.1)(jiti@1.21.7)(jsdom@24.1.3)(yaml@2.7.0) + vitest-browser-react: + specifier: ^0.1.1 + version: 0.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(@vitest/browser@3.1.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(vitest@3.1.1) + vitest-dom: + specifier: ^0.1.1 + version: 0.1.1(vitest@3.1.1) + + packages/design-system: + dependencies: + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/react': + specifier: ^18.0.0 + version: 18.3.18 + '@types/react-dom': + specifier: ^18.0.0 + version: 18.3.5(@types/react@18.3.18) + typescript: + specifier: ^4.6.0 + version: 4.9.5 + + packages/task-manager: dependencies: + '@lateral/design-system': + specifier: workspace:* + version: link:../design-system '@supabase/supabase-js': specifier: ^2.49.4 version: 2.49.4 @@ -476,6 +570,68 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.6.0': + resolution: {integrity: sha512-WhCn7Z7TauhBtmzhvKpoQs0Wwb/kBcy4CwpuI0/eEIr2Lx2auxmulAzLr91wVZJaz47iUZdkXOK7WlAfxGKCnA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.1': + resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.12.0': + resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.24.0': + resolution: {integrity: sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -514,6 +670,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pkgr/core@0.2.2': + resolution: {integrity: sha512-25L86MyPvnlQoX2MTIV2OiUcb6vJ6aRbFa9pbwByn95INKD5mFH2smgjDhq+fwJoqAgvgbdJLj6Tz7V9X5CFAQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -617,6 +777,9 @@ packages: cpu: [x64] os: [win32] + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@supabase/auth-js@2.69.1': resolution: {integrity: sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==} @@ -655,6 +818,12 @@ packages: '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/node@22.14.0': resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} @@ -675,6 +844,53 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/eslint-plugin@8.29.1': + resolution: {integrity: sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.29.1': + resolution: {integrity: sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.29.1': + resolution: {integrity: sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.29.1': + resolution: {integrity: sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.29.1': + resolution: {integrity: sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.29.1': + resolution: {integrity: sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.29.1': + resolution: {integrity: sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.29.1': + resolution: {integrity: sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-react@3.1.0': resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} engines: {node: ^14.18.0 || >=16.0.0} @@ -725,10 +941,23 @@ packages: '@vitest/utils@3.1.1': resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -759,13 +988,52 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -776,6 +1044,10 @@ packages: peerDependencies: postcss: ^8.1.0 + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -783,6 +1055,9 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} @@ -803,6 +1078,18 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -845,6 +1132,9 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -871,6 +1161,26 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.0: resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} @@ -887,6 +1197,17 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -901,6 +1222,10 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -927,6 +1252,10 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -935,6 +1264,10 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + es-module-lexer@1.6.0: resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} @@ -946,6 +1279,14 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + esbuild@0.18.20: resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} engines: {node: '>=12'} @@ -960,24 +1301,167 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.2: + resolution: {integrity: sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-prettier@5.2.6: + resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.24.0: + resolution: {integrity: sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + expect-type@1.2.1: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fastq@1.19.0: resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -1002,6 +1486,13 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1014,6 +1505,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1030,6 +1525,14 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -1037,10 +1540,24 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -1069,30 +1586,93 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + indent-string@5.0.0: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1100,19 +1680,66 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} - jiti@1.21.7: + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + jsdom@24.1.3: resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==} engines: {node: '>=18'} @@ -1127,11 +1754,35 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1139,9 +1790,16 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1190,10 +1848,16 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} @@ -1213,6 +1877,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} @@ -1235,12 +1902,64 @@ packages: resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} engines: {node: '>= 6'} + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parse5@7.2.1: resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1252,6 +1971,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path@0.12.7: + resolution: {integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1284,6 +2006,10 @@ packages: engines: {node: '>=18'} hasBin: true + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss-import@15.1.0: resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -1325,10 +2051,30 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} @@ -1347,6 +2093,9 @@ packages: peerDependencies: react: ^18.3.1 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} @@ -1369,17 +2118,33 @@ packages: resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} engines: {node: '>=12'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} hasBin: true + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -1403,6 +2168,18 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1417,6 +2194,23 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1425,6 +2219,22 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1454,6 +2264,25 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1462,10 +2291,18 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-indent@4.0.0: resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} engines: {node: '>=12'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -1482,6 +2319,10 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + synckit@0.11.3: + resolution: {integrity: sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==} + engines: {node: ^14.18.0 || >=16.0.0} + tailwindcss@3.4.17: resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} engines: {node: '>=14.0.0'} @@ -1531,6 +2372,12 @@ packages: resolution: {integrity: sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==} engines: {node: '>=18'} + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -1544,11 +2391,48 @@ packages: typescript: optional: true + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.29.1: + resolution: {integrity: sha512-f8cDkvndhbQMPcysk6CUSGBWV+g1utqdn71P5YKwMumVMOG/5k7cHq0KyG4O52nB0oKS4aN2Tp5+wB4APJGC+w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} hasBin: true + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -1562,12 +2446,18 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util@0.10.4: + resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} + vite-node@3.1.1: resolution: {integrity: sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1724,6 +2614,22 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1734,6 +2640,10 @@ packages: engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1769,6 +2679,10 @@ packages: engines: {node: '>= 14'} hasBin: true + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + zustand@5.0.3: resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} engines: {node: '>=12.20.0'} @@ -2079,6 +2993,67 @@ snapshots: '@esbuild/win32-x64@0.25.2': optional: true + '@eslint-community/eslint-utils@4.6.0(eslint@9.24.0(jiti@1.21.7))': + dependencies: + eslint: 9.24.0(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.1': {} + + '@eslint/core@0.12.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.24.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2120,6 +3095,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@pkgr/core@0.2.2': {} + '@polka/url@1.0.0-next.29': {} '@rollup/rollup-android-arm-eabi@4.39.0': @@ -2182,6 +3159,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.39.0': optional: true + '@rtsao/scc@1.1.0': {} + '@supabase/auth-js@2.69.1': dependencies: '@supabase/node-fetch': 2.6.15 @@ -2243,6 +3222,10 @@ snapshots: '@types/estree@1.0.7': {} + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + '@types/node@22.14.0': dependencies: undici-types: 6.21.0 @@ -2264,6 +3247,83 @@ snapshots: dependencies: '@types/node': 22.14.0 + '@typescript-eslint/eslint-plugin@8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5))(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + '@typescript-eslint/scope-manager': 8.29.1 + '@typescript-eslint/type-utils': 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + '@typescript-eslint/utils': 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + '@typescript-eslint/visitor-keys': 8.29.1 + eslint: 9.24.0(jiti@1.21.7) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5)': + dependencies: + '@typescript-eslint/scope-manager': 8.29.1 + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/typescript-estree': 8.29.1(typescript@4.9.5) + '@typescript-eslint/visitor-keys': 8.29.1 + debug: 4.4.0 + eslint: 9.24.0(jiti@1.21.7) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.29.1': + dependencies: + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/visitor-keys': 8.29.1 + + '@typescript-eslint/type-utils@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5)': + dependencies: + '@typescript-eslint/typescript-estree': 8.29.1(typescript@4.9.5) + '@typescript-eslint/utils': 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + debug: 4.4.0 + eslint: 9.24.0(jiti@1.21.7) + ts-api-utils: 2.1.0(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.29.1': {} + + '@typescript-eslint/typescript-estree@8.29.1(typescript@4.9.5)': + dependencies: + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/visitor-keys': 8.29.1 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@4.9.5) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5)': + dependencies: + '@eslint-community/eslint-utils': 4.6.0(eslint@9.24.0(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.29.1 + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/typescript-estree': 8.29.1(typescript@4.9.5) + eslint: 9.24.0(jiti@1.21.7) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.29.1': + dependencies: + '@typescript-eslint/types': 8.29.1 + eslint-visitor-keys: 4.2.0 + '@vitejs/plugin-react@3.1.0(vite@4.5.13(@types/node@22.14.0))': dependencies: '@babel/core': 7.26.9 @@ -2342,8 +3402,21 @@ snapshots: loupe: 3.1.3 tinyrainbow: 2.0.0 + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + agent-base@7.1.3: {} + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -2365,12 +3438,81 @@ snapshots: arg@5.0.2: {} + argparse@2.0.1: {} + aria-query@5.3.0: dependencies: dequal: 2.0.3 + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + async-function@1.0.0: {} + asynckit@0.4.0: {} autoprefixer@10.4.20(postcss@8.5.3): @@ -2383,15 +3525,24 @@ snapshots: postcss: 8.5.3 postcss-value-parser: 4.2.0 + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + balanced-match@1.0.2: {} binary-extensions@2.3.0: {} - brace-expansion@2.0.1: + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 + concat-map: 0.0.1 - braces@3.0.3: + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -2409,6 +3560,20 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + camelcase-css@2.0.1: {} caniuse-lite@1.0.30001700: {} @@ -2454,6 +3619,8 @@ snapshots: commander@4.1.1: {} + concat-map@0.0.1: {} + convert-source-map@2.0.0: {} cross-spawn@7.0.6: @@ -2478,6 +3645,28 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.4.0: dependencies: ms: 2.1.3 @@ -2486,6 +3675,20 @@ snapshots: deep-eql@5.0.2: {} + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + delayed-stream@1.0.0: {} dequal@2.0.3: {} @@ -2494,6 +3697,10 @@ snapshots: dlv@1.1.3: {} + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -2514,10 +3721,83 @@ snapshots: entities@4.5.0: {} + es-abstract@1.23.9: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + es-define-property@1.0.1: {} es-errors@1.3.0: {} + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + es-module-lexer@1.6.0: {} es-object-atoms@1.1.1: @@ -2531,6 +3811,16 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + esbuild@0.18.20: optionalDependencies: '@esbuild/android-arm': 0.18.20 @@ -2586,12 +3876,173 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.2(eslint@9.24.0(jiti@1.21.7)): + dependencies: + eslint: 9.24.0(jiti@1.21.7) + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint@9.24.0(jiti@1.21.7)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + eslint: 9.24.0(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5))(eslint@9.24.0(jiti@1.21.7)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.24.0(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint@9.24.0(jiti@1.21.7)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.2(eslint@9.24.0(jiti@1.21.7)))(eslint@9.24.0(jiti@1.21.7))(prettier@3.5.3): + dependencies: + eslint: 9.24.0(jiti@1.21.7) + prettier: 3.5.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.3 + optionalDependencies: + eslint-config-prettier: 10.1.2(eslint@9.24.0(jiti@1.21.7)) + + eslint-plugin-react-hooks@5.2.0(eslint@9.24.0(jiti@1.21.7)): + dependencies: + eslint: 9.24.0(jiti@1.21.7) + + eslint-plugin-react@7.37.5(eslint@9.24.0(jiti@1.21.7)): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.24.0(jiti@1.21.7) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.3.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.24.0(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.6.0(eslint@9.24.0(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 + '@eslint/core': 0.12.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.24.0 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.7 + esutils@2.0.3: {} + expect-type@1.2.1: {} + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2600,14 +4051,38 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fastq@1.19.0: dependencies: reusify: 1.0.4 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.6 @@ -2630,6 +4105,17 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + gensync@1.0.0-beta.2: {} get-intrinsic@1.3.0: @@ -2650,6 +4136,12 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -2669,12 +4161,31 @@ snapshots: globals@11.12.0: {} + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + globrex@0.1.2: {} gopd@1.2.0: {} + graphemer@1.4.0: {} + + has-bigints@1.1.0: {} + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -2707,30 +4218,151 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + indent-string@5.0.0: {} + inherits@2.0.3: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-map@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-number@7.0.0: {} is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + isexe@2.0.0: {} + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -2741,6 +4373,10 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + jsdom@24.1.3: dependencies: cssstyle: 4.3.0 @@ -2771,14 +4407,46 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + json5@2.2.3: {} + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash-es@4.17.21: {} + lodash.merge@4.6.2: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -2818,10 +4486,16 @@ snapshots: min-indent@1.0.1: {} + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 + minimist@1.2.8: {} + minipass@7.1.2: {} mrmime@2.0.1: {} @@ -2836,6 +4510,8 @@ snapshots: nanoid@3.3.8: {} + natural-compare@1.4.0: {} + node-releases@2.0.19: {} normalize-path@3.0.0: {} @@ -2848,12 +4524,81 @@ snapshots: object-hash@3.0.0: {} + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parse5@7.2.1: dependencies: entities: 4.5.0 + path-exists@4.0.0: {} + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -2863,6 +4608,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path@0.12.7: + dependencies: + process: 0.11.10 + util: 0.10.4 + pathe@2.0.3: {} pathval@2.0.0: {} @@ -2883,6 +4633,8 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + possible-typed-array-names@1.1.0: {} + postcss-import@15.1.0(postcss@8.5.3): dependencies: postcss: 8.5.3 @@ -2920,12 +4672,28 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.5.3: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 ansi-styles: 5.2.0 react-is: 17.0.2 + process@0.11.10: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + psl@1.15.0: dependencies: punycode: 2.3.1 @@ -2942,6 +4710,8 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-is@16.13.1: {} + react-is@17.0.2: {} react-refresh@0.14.2: {} @@ -2963,16 +4733,44 @@ snapshots: indent-string: 5.0.0 strip-indent: 4.0.0 + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + regenerator-runtime@0.14.1: {} + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + requires-port@1.0.0: {} + resolve-from@4.0.0: {} + resolve@1.22.10: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + reusify@1.0.4: {} rollup@3.29.5: @@ -3013,6 +4811,25 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + safer-buffer@2.1.2: {} saxes@6.0.0: @@ -3025,12 +4842,64 @@ snapshots: semver@6.3.1: {} + semver@7.7.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -3059,6 +4928,50 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.9 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -3067,10 +4980,14 @@ snapshots: dependencies: ansi-regex: 6.1.0 + strip-bom@3.0.0: {} + strip-indent@4.0.0: dependencies: min-indent: 1.0.1 + strip-json-comments@3.1.1: {} + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -3089,6 +5006,11 @@ snapshots: symbol-tree@3.2.4: {} + synckit@0.11.3: + dependencies: + '@pkgr/core': 0.2.2 + tslib: 2.8.1 + tailwindcss@3.4.17: dependencies: '@alloc/quick-lru': 5.2.0 @@ -3153,14 +5075,81 @@ snapshots: dependencies: punycode: 2.3.1 + ts-api-utils@2.1.0(typescript@4.9.5): + dependencies: + typescript: 4.9.5 + ts-interface-checker@0.1.13: {} tsconfck@3.1.5(typescript@4.9.5): optionalDependencies: typescript: 4.9.5 + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5): + dependencies: + '@typescript-eslint/eslint-plugin': 8.29.1(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5))(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + '@typescript-eslint/utils': 8.29.1(eslint@9.24.0(jiti@1.21.7))(typescript@4.9.5) + eslint: 9.24.0(jiti@1.21.7) + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + typescript@4.9.5: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + undici-types@6.21.0: {} universalify@0.2.0: {} @@ -3171,6 +5160,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + url-parse@1.5.10: dependencies: querystringify: 2.2.0 @@ -3178,6 +5171,10 @@ snapshots: util-deprecate@1.0.2: {} + util@0.10.4: + dependencies: + inherits: 2.0.3 + vite-node@3.1.1(@types/node@22.14.0)(jiti@1.21.7)(yaml@2.7.0): dependencies: cac: 6.7.14 @@ -3314,6 +5311,47 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3323,6 +5361,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -3345,6 +5385,8 @@ snapshots: yaml@2.7.0: {} + yocto-queue@0.1.0: {} + zustand@5.0.3(@types/react@18.3.18)(react@18.3.1): optionalDependencies: '@types/react': 18.3.18 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..18ec407 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/src/components/design-system/Button/index.ts b/src/components/design-system/Button/index.ts deleted file mode 100644 index e22c29a..0000000 --- a/src/components/design-system/Button/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Button"; diff --git a/src/components/design-system/Button/styles.tsx b/src/components/design-system/Button/styles.tsx deleted file mode 100644 index 52ce709..0000000 --- a/src/components/design-system/Button/styles.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export const baseStyles = "px-4 py-2 rounded-md transition-colors font-medium disabled:cursor-not-allowed"; - -export const variantStyles = { - primary: "bg-blue-500 hover:bg-blue-600 text-white disabled:bg-blue-400", - secondary: "bg-gray-100 text-gray-700 hover:bg-gray-200 disabled:bg-gray-200 disabled:text-gray-400", - danger: "bg-red-500 hover:bg-red-600 text-white disabled:bg-red-400", -}; diff --git a/src/components/design-system/ConfirmDialog/index.ts b/src/components/design-system/ConfirmDialog/index.ts deleted file mode 100644 index de6dcaf..0000000 --- a/src/components/design-system/ConfirmDialog/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./ConfirmDialog"; -export * from "./types"; diff --git a/src/components/design-system/Input/index.ts b/src/components/design-system/Input/index.ts deleted file mode 100644 index be66d76..0000000 --- a/src/components/design-system/Input/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Input"; diff --git a/src/components/design-system/Input/styles.tsx b/src/components/design-system/Input/styles.tsx deleted file mode 100644 index f5092a9..0000000 --- a/src/components/design-system/Input/styles.tsx +++ /dev/null @@ -1 +0,0 @@ -export const baseStyles = "border border-gray-300 py-2 px-4 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"; diff --git a/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx b/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx deleted file mode 100644 index b256e57..0000000 --- a/src/components/design-system/LoadingIndicator/LoadingIndicator.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -export const LoadingIndicator = () => ( -
    -
    -
    -); diff --git a/src/components/design-system/README.md b/src/components/design-system/README.md deleted file mode 100644 index 32ce829..0000000 --- a/src/components/design-system/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# UI Design System - -Contains Atomic design components and may be later created as a separate package so it can be reused accross multiple projects. \ No newline at end of file diff --git a/src/components/design-system/Toast/Toast.tsx b/src/components/design-system/Toast/Toast.tsx deleted file mode 100644 index e61223c..0000000 --- a/src/components/design-system/Toast/Toast.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useEffect } from "react"; - -import { ToastProps } from "./types"; -import { colorClasses, toastClasses } from "./styles"; -import { TOAST_AUTO_CLOSE_TIMEOUT } from "./constants"; - -export const Toast: React.FC = ({ - message, - onClose, - type = "success", -}) => { - useEffect(() => { - const timer = setTimeout(onClose, TOAST_AUTO_CLOSE_TIMEOUT); - - return () => clearTimeout(timer); - }, [onClose]); - - return ( -
    - {message} - -
    - ); -}; diff --git a/src/components/design-system/index.ts b/src/components/design-system/index.ts deleted file mode 100644 index 5a61d6e..0000000 --- a/src/components/design-system/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./Button"; -export * from "./Input"; -export * from "./LoadingIndicator"; -export * from "./ConfirmDialog"; -export * from "./Toast"; diff --git a/src/components/task-manager/components/filter/TaskFilter.tsx b/src/components/task-manager/components/filter/TaskFilter.tsx deleted file mode 100644 index 9ae7bb8..0000000 --- a/src/components/task-manager/components/filter/TaskFilter.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from "react"; -import { Button } from "../../../design-system"; -import type { TaskFilterProps } from "./types"; - -export const TaskFilter: React.FC = ({ - currentFilter, - onFilterChange -}) => { - return ( -
    - - - -
    - ); -}; diff --git a/src/components/task-manager/components/filter/index.ts b/src/components/task-manager/components/filter/index.ts deleted file mode 100644 index 2144c3c..0000000 --- a/src/components/task-manager/components/filter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./TaskFilter"; diff --git a/src/components/task-manager/components/form/TaskForm.tsx b/src/components/task-manager/components/form/TaskForm.tsx deleted file mode 100644 index 5594a75..0000000 --- a/src/components/task-manager/components/form/TaskForm.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useState, useCallback } from "react"; -import type { TaskFormProps } from "./types"; -import { Button, Input } from "../../../design-system"; - -export const TaskForm: React.FC = ({ onAddTask }) => { - const [newTask, setNewTask] = useState(""); - const sanitizedTask = newTask.trim(); - - const handleSubmit = useCallback((e: React.FormEvent) => { - e.preventDefault(); - if (!sanitizedTask) return; - - onAddTask(sanitizedTask); - setNewTask(""); - }, [sanitizedTask, onAddTask]); - - return ( -
    - setNewTask(e.target.value)} - /> - -
    - ); -}; diff --git a/src/components/task-manager/components/form/index.ts b/src/components/task-manager/components/form/index.ts deleted file mode 100644 index 4949fb2..0000000 --- a/src/components/task-manager/components/form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./TaskForm"; diff --git a/src/components/task-manager/components/index.ts b/src/components/task-manager/components/index.ts deleted file mode 100644 index 21f55f2..0000000 --- a/src/components/task-manager/components/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./form/TaskForm"; -export * from "./filter/TaskFilter"; -export * from "./item/TaskItem"; -export * from "./list/TasksList"; -export * from "./manager/TaskManager"; diff --git a/src/components/task-manager/components/list/TasksList.tsx b/src/components/task-manager/components/list/TasksList.tsx deleted file mode 100644 index 6a09072..0000000 --- a/src/components/task-manager/components/list/TasksList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; -import { TaskItem } from "../item/TaskItem"; -import type { TasksListProps } from "./types"; - -export const TasksList: React.FC = ({ - tasks, - onDelete, - onToggle -}) => ( -
    - {tasks.length > 0 ? ( -
      - {tasks.map((task) => ( - - ))} -
    - ) : ( -

    No tasks found

    - )} -
    -); - - diff --git a/src/components/task-manager/components/list/index.ts b/src/components/task-manager/components/list/index.ts deleted file mode 100644 index f13a2b0..0000000 --- a/src/components/task-manager/components/list/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./TasksList"; - diff --git a/src/components/task-manager/index.ts b/src/components/task-manager/index.ts deleted file mode 100644 index cd59e0d..0000000 --- a/src/components/task-manager/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./components/manager/TaskManager"; diff --git a/src/components/task-manager/store/helpers.tsx b/src/components/task-manager/store/helpers.tsx deleted file mode 100644 index 08ad51a..0000000 --- a/src/components/task-manager/store/helpers.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Task, FilterType } from "./models"; - -export const getFilteredTasks = (tasks: Task[], filter: FilterType): Task[] => { - return tasks.filter((task) => { - if (filter === "completed") return task.completed === true; - if (filter === "pending") return task.completed === false; - return true; - }); -}; diff --git a/src/components/task-manager/store/index.ts b/src/components/task-manager/store/index.ts deleted file mode 100644 index 6c5dcd3..0000000 --- a/src/components/task-manager/store/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './models'; -export { useTasksStore } from './taskStore'; diff --git a/src/main.tsx b/src/main.tsx deleted file mode 100644 index 17a9e4e..0000000 --- a/src/main.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; - -import "./index.css"; - -import { App } from "./App"; - -ReactDOM.createRoot(document.getElementById("root")!).render( - - - -); diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 70a2bb5..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "lib": ["esnext", "dom"], - "module": "ESNext", - "moduleResolution": "Bundler", - "jsx": "react-jsx", - "types": ["@vitest/browser/providers/playwright"], - "strict": true, - "declaration": true, - "noEmit": true, - "esModuleInterop": true, - "skipLibCheck": true - } - } \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts deleted file mode 100644 index 081c8d9..0000000 --- a/vite.config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; - -export default defineConfig({ - plugins: [react()], -}); diff --git a/vitest.config.ts b/vitest.config.ts index 89f1385..32895e9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,15 +1,12 @@ import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' +import path from 'path' export default defineConfig({ plugins: [react()], - test: { - browser: { - enabled: true, - provider: 'playwright', - instances: [ - { browser: 'chromium' }, - ], + resolve: { + alias: { + '@lateral/design-system': path.resolve(__dirname, './packages/design-system/src') } } }) \ No newline at end of file diff --git a/vitest.workspace.ts b/vitest.workspace.ts index 410e6fd..a67953a 100644 --- a/vitest.workspace.ts +++ b/vitest.workspace.ts @@ -11,6 +11,9 @@ export default defineWorkspace([ provider: 'playwright', // https://vitest.dev/guide/browser/playwright instances: [ + { + browser: 'chromium' + } ], }, }, From ebaf211a3c2db114a8875fad4dd5eb2defe4b6a8 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 20:25:03 -0300 Subject: [PATCH 17/25] chore: fixes vercel deployment --- .github/workflows/deploy-to-vercel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-to-vercel.yml b/.github/workflows/deploy-to-vercel.yml index 02b382e..8de5d59 100644 --- a/.github/workflows/deploy-to-vercel.yml +++ b/.github/workflows/deploy-to-vercel.yml @@ -58,5 +58,5 @@ jobs: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} - working-directory: packages/task-manager + working-directory: ./packages/task-manager vercel-args: '--prod' From 53197a1b5fed1e7e7b8cab945794a2650c74c4f2 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 20:34:49 -0300 Subject: [PATCH 18/25] chore: add playwright browser installation --- .github/workflows/deploy-to-vercel.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/deploy-to-vercel.yml b/.github/workflows/deploy-to-vercel.yml index 8de5d59..a1e3583 100644 --- a/.github/workflows/deploy-to-vercel.yml +++ b/.github/workflows/deploy-to-vercel.yml @@ -41,6 +41,9 @@ jobs: - name: Install dependencies run: pnpm install + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps chromium + - name: Run linting run: pnpm lint From eff9f7f353c6d54fad6dc6f46c14c0ce20d9adf0 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 20:44:43 -0300 Subject: [PATCH 19/25] chore: fixes github action --- .github/workflows/deploy-to-vercel.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-to-vercel.yml b/.github/workflows/deploy-to-vercel.yml index a1e3583..21d0fc2 100644 --- a/.github/workflows/deploy-to-vercel.yml +++ b/.github/workflows/deploy-to-vercel.yml @@ -47,13 +47,14 @@ jobs: - name: Run linting run: pnpm lint - - name: Run tests - run: pnpm test - - - name: Build application + - name: Build application and dependencies if: ${{ success() }} run: pnpm build + - name: Run tests + if: ${{ success() }} + run: pnpm test + - name: Deploy to Vercel if: ${{ success() }} uses: amondnet/vercel-action@v25 From 91c08471e1949f203d4593c0d1e1267a287efad8 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 21:00:31 -0300 Subject: [PATCH 20/25] chore: splits github action build and test jobs into smaller ones to avoid timeouts --- .github/workflows/deploy-to-vercel.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-to-vercel.yml b/.github/workflows/deploy-to-vercel.yml index 21d0fc2..3daecd9 100644 --- a/.github/workflows/deploy-to-vercel.yml +++ b/.github/workflows/deploy-to-vercel.yml @@ -47,13 +47,21 @@ jobs: - name: Run linting run: pnpm lint - - name: Build application and dependencies + - name: Build Design System package if: ${{ success() }} - run: pnpm build + run: pnpm build:ds + + - name: Run Design System tests + if: ${{ success() }} + run: pnpm --filter "@lateral/design-system" test - - name: Run tests + - name: Build Task Manager app + if: ${{ success() }} + run: pnpm --filter "@lateral/task-manager" build + + - name: Run Task Manager tests if: ${{ success() }} - run: pnpm test + run: pnpm --filter "@lateral/task-manager" test - name: Deploy to Vercel if: ${{ success() }} From e4861d1c91b32ce9d11d31cd25aaa6bd8810afbd Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 21:05:01 -0300 Subject: [PATCH 21/25] chore: fixes branch name --- .github/workflows/deploy-to-vercel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-to-vercel.yml b/.github/workflows/deploy-to-vercel.yml index 3daecd9..9daf9ef 100644 --- a/.github/workflows/deploy-to-vercel.yml +++ b/.github/workflows/deploy-to-vercel.yml @@ -3,7 +3,7 @@ name: Deploy to Vercel on: push: branches: - - master + - main workflow_dispatch: jobs: From ee5f0653ddc7f97d8eb0da1c72423a597acedc1f Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 21:23:50 -0300 Subject: [PATCH 22/25] chore: temporarily disables vitest --- .github/workflows/deploy-to-vercel.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-to-vercel.yml b/.github/workflows/deploy-to-vercel.yml index 9daf9ef..4957aca 100644 --- a/.github/workflows/deploy-to-vercel.yml +++ b/.github/workflows/deploy-to-vercel.yml @@ -60,7 +60,9 @@ jobs: run: pnpm --filter "@lateral/task-manager" build - name: Run Task Manager tests - if: ${{ success() }} + # if: ${{ success() }} + # Temporarily disabled due to Error: Vitest failed to find the runner. This is a bug in Vitest. Please, open an issue with reproduction. + if: false run: pnpm --filter "@lateral/task-manager" test - name: Deploy to Vercel From df6f74cb8e966618e9ba3e2668b3b5a54a75e190 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 22:03:00 -0300 Subject: [PATCH 23/25] chore: adds vercel config --- vercel.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 vercel.json diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..09263e4 --- /dev/null +++ b/vercel.json @@ -0,0 +1,6 @@ +{ + "framework": "vite", + "buildCommand": "pnpm build", + "installCommand": "pnpm install", + "outputDirectory": "packages/task-manager/dist" +} \ No newline at end of file From dc69ec2860b4b6dedc3cc9a57be667af1e598066 Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 22:09:24 -0300 Subject: [PATCH 24/25] chore: updates vercel config --- .github/workflows/deploy-to-vercel.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-to-vercel.yml b/.github/workflows/deploy-to-vercel.yml index 4957aca..6e3b72c 100644 --- a/.github/workflows/deploy-to-vercel.yml +++ b/.github/workflows/deploy-to-vercel.yml @@ -66,7 +66,9 @@ jobs: run: pnpm --filter "@lateral/task-manager" test - name: Deploy to Vercel - if: ${{ success() }} + # if: ${{ success() }} + # Temporarily disabled because this project is configured to use the Vercel GitHub integration for deployment + if: false uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} From 9bb4a9da972380b612b245357e47408b133e71ed Mon Sep 17 00:00:00 2001 From: Wesley Costa Date: Sat, 12 Apr 2025 22:26:18 -0300 Subject: [PATCH 25/25] chore: updates readme --- README.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5a35036..fc81a96 100644 --- a/README.md +++ b/README.md @@ -57,43 +57,44 @@ The Task Manager app includes the following core features: - **Deleting Tasks:** Remove tasks from the list. - **Filtering Tasks:** Filter tasks by status (All, Completed, Pending). -### Issues to Address +### Issues to Addressed -While reviewing the project, please identify and resolve the following issues: +While reviewing the project, I have identified and resolved the following issues: 1. **Task Filter Bug:** - - The filter functionality is not working as expected. Changing the filter does not update the task list correctly. + - The filter functionality is now working as expected. Changing the filter updates the task list correctly. 2. **Task Deletion Issue:** - - Deleting a task does not immediately update the UI, causing a delay or inconsistency in the displayed task list. + - Deleting a task immediately updates the UI. A confirmation dialog has been added as well. 3. **Styling Inconsistencies:** - - Some UI elements are not using Tailwind CSS classes consistently. Ensure the layout, spacing, and colors align with the provided design guidelines. + - UI elements are using Tailwind CSS classes consistently. Layout, spacing, and colors have been updated to align with the provided design guidelines. 4. **TypeScript Warnings/Errors:** - - Several components may have missing or incorrect type definitions. Address all TypeScript warnings to ensure robust type safety. + - Type definitions have been added. All TypeScript warnings have been addressed to ensure robust type safety. 5. **Code Refactoring:** - - The current code structure has redundant logic and lacks modularity. Refactor the code to improve readability and maintainability. + - The code structure has been refactored using a monorepo to improve readability and maintainability. ### Optional Enhancements If time permits, consider implementing one or more of the following: - **Persistence:** - - Implement functionality to save and retrieve tasks from local storage or a remote API, ensuring that the task list persists across page reloads. + - Implemented functionality to save and retrieve tasks from a remote API using Supabse, ensuring that the task list persists across page reloads. - **Improved UI/UX:** - - Enhance the user interface with additional styling improvements or animations to improve user experience. - - Implement a confirmation dialog when deleting a task. + - Enhanced the user interface with additional styling improvements improve user experience, like a sticky form. So while the user scrolls down, he/she can still see the add form. This is specially usefeull on mobile. + - Implemented a confirmation dialog when deleting a task. + - A mobile first, responsive design has been implemented to improve user experience on mobile - **Improved UI/UX:** - - Deploy the app - - Create a CI/CD pipeline that will automatically deploy/release the app when changes were made. + - The app has been depoyed to Vercel. You can access it on this link: https://react-simple-task-manager.vercel.app/ + - A CI/CD pipeline has been created to automatically deploy/release the app when changes were made. (minor issues have to be address later) - **Unit Testing:** - - Write tests for key components using your preferred testing framework (e.g., Jest, React Testing Library). + - Unit tests for key components have been added using Vitest ## Submission Instructions