diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 4b23e3d..0000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "project": "./tsconfig.json"
- },
- "plugins": ["@typescript-eslint"],
- "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended", "@it-incubator/eslint-config"],
- "rules": {
- "@typescript-eslint/consistent-type-imports": "warn"
- }
-}
diff --git a/.idea/biome.xml b/.idea/biome.xml
new file mode 100644
index 0000000..cb2a0dc
--- /dev/null
+++ b/.idea/biome.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
new file mode 100644
index 0000000..d23208f
--- /dev/null
+++ b/.idea/jsLibraryMappings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..3876841
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
+ "organizeImports": {
+ "enabled": true
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "nursery": {
+ "useSortedClasses": "warn"
+ }
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "semicolons": "asNeeded"
+ }
+ },
+ "formatter": { "indentStyle": "space", "indentWidth": 2 }
+}
diff --git a/bun.lockb b/bun.lockb
index f52c32c..6d62a4b 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..af7b002
--- /dev/null
+++ b/components.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "src/styles/globals.css",
+ "baseColor": "zinc",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils"
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index e4a7554..6407f00 100644
--- a/package.json
+++ b/package.json
@@ -1,37 +1,43 @@
{
- "name": "todolist_next",
+ "name": "todolist",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
- "lint": "next lint",
- "start": "next start"
+ "lint": "biome lint --write --unsafe ./src",
+ "start": "next start",
+ "format": "prettier --write ."
},
"dependencies": {
- "@tanstack/react-query": "^4.28.0",
- "axios": "^1.3.4",
- "next": "13.2.4",
- "react": "18.2.0",
- "react-dom": "18.2.0",
- "zod": "^3.21.4"
+ "@radix-ui/react-checkbox": "^1.1.1",
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
+ "@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-toggle-group": "^1.1.0",
+ "@tanstack/react-query": "^5.51.23",
+ "async-mutex": "^0.5.0",
+ "axios": "^1.7.4",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.427.0",
+ "next": "^14.2.5",
+ "next-themes": "^0.3.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "tailwind-merge": "^2.5.2",
+ "tailwindcss-animate": "^1.0.7",
+ "zod": "^3.23.8"
},
"devDependencies": {
- "@it-incubator/eslint-config": "^0.1.0",
- "@it-incubator/prettier-config": "^0.1.0",
- "@types/node": "^18.15.11",
- "@types/react": "^18.0.33",
- "@types/react-dom": "^18.0.11",
- "@typescript-eslint/eslint-plugin": "^5.57.1",
- "@typescript-eslint/parser": "^5.57.1",
- "autoprefixer": "^10.4.14",
- "eslint": "^8.37.0",
- "eslint-config-next": "13.2.4",
- "postcss": "^8.4.21",
- "prettier": "^2.8.7",
- "prettier-plugin-tailwindcss": "^0.2.6",
- "tailwindcss": "^3.3.1",
- "typescript": "^5.0.3"
+ "@biomejs/biome": "1.8.3",
+ "@types/node": "^22.3.0",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.41",
+ "tailwindcss": "^3.4.10",
+ "typescript": "^5.5.4"
},
"ct3aMetadata": {
"initVersion": "6.10.1"
diff --git a/prettier.config.cjs b/prettier.config.cjs
deleted file mode 100644
index accc933..0000000
--- a/prettier.config.cjs
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import("prettier").Config} */
-module.exports = {
- plugins: [require.resolve("prettier-plugin-tailwindcss"), require.resolve("@it-incubator/prettier-config")],
-};
diff --git a/src/components/auth-redirect/index.tsx b/src/components/auth-redirect/index.tsx
index 1ac843d..280cb02 100644
--- a/src/components/auth-redirect/index.tsx
+++ b/src/components/auth-redirect/index.tsx
@@ -1,30 +1,31 @@
-import type { FC, ReactNode } from "react";
-import { useEffect } from "react";
+import type { FC, ReactNode } from "react"
+import { useEffect } from "react"
-import { useRouter } from "next/router";
+import { useRouter } from "next/router"
-import { Loader } from "../loader";
+import { Loader } from "../ui/loader"
-import { useMeQuery } from "@/services";
+import { useMeQuery } from "@/services"
export const AuthRedirect: FC<{ children: ReactNode }> = ({ children }) => {
- const router = useRouter();
- const { data: user, isLoading, isError } = useMeQuery();
- const isAuthPage = router.pathname === "/login";
+ const router = useRouter()
+ const { data: user, isLoading } = useMeQuery()
+ const isAuthPage =
+ router.pathname === "/login" || router.pathname === "/sign-up"
useEffect(() => {
if (!isLoading && !user && !isAuthPage) {
- router.push("/login");
+ router.push("/login")
}
- }, [user, isError, isLoading, isAuthPage, router]);
+ }, [user, isLoading, isAuthPage, router])
if (isLoading || (!user && !isAuthPage)) {
return (
- );
+ )
}
- return <>{children}>;
-};
+ return <>{children}>
+}
diff --git a/src/components/button/index.tsx b/src/components/button/index.tsx
deleted file mode 100644
index f67bf8f..0000000
--- a/src/components/button/index.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { ButtonHTMLAttributes, DetailedHTMLProps, FC } from "react";
-
-type Props = DetailedHTMLProps<
- ButtonHTMLAttributes,
- HTMLButtonElement
->;
-
-export const Button: FC = ({ className, ...rest }) => {
- return (
-
-
-
- );
-};
diff --git a/src/components/fullscreen-loader/index.tsx b/src/components/fullscreen-loader/index.tsx
index c6a6a64..cb3c722 100644
--- a/src/components/fullscreen-loader/index.tsx
+++ b/src/components/fullscreen-loader/index.tsx
@@ -1,9 +1,9 @@
-import { Loader } from "../loader";
+import { Loader } from "../ui/loader"
export const FullscreenLoader = () => {
return (
- );
-};
+ )
+}
diff --git a/src/components/index.ts b/src/components/index.ts
index bb544b5..766c5c7 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -1,6 +1,6 @@
-export * from "./todolist";
-export * from "./auth-redirect";
-export * from "./button";
-export * from "./input";
-export * from "./loader";
-export * from "./fullscreen-loader";
+export * from "./todolist"
+export * from "./auth-redirect"
+export * from "./ui/button"
+export * from "./ui/input"
+export * from "./ui/loader"
+export * from "./fullscreen-loader"
diff --git a/src/components/input/index.tsx b/src/components/input/index.tsx
deleted file mode 100644
index f63056d..0000000
--- a/src/components/input/index.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { DetailedHTMLProps, FC, InputHTMLAttributes } from "react";
-
-type Props = DetailedHTMLProps<
- InputHTMLAttributes,
- HTMLInputElement
->;
-
-export const Input: FC = ({ className, ...rest }) => {
- return (
-
-
-
- );
-};
diff --git a/src/components/mode-toggle.tsx b/src/components/mode-toggle.tsx
new file mode 100644
index 0000000..7e613c4
--- /dev/null
+++ b/src/components/mode-toggle.tsx
@@ -0,0 +1,40 @@
+"use client"
+
+import { Moon, Sun } from "lucide-react"
+import { useTheme } from "next-themes"
+import * as React from "react"
+
+import { Button } from "@/components/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+
+export function ModeToggle() {
+ const { setTheme } = useTheme()
+
+ return (
+
+
+
+
+
+ setTheme("light")}>
+ Light
+
+ setTheme("dark")}>
+ Dark
+
+ setTheme("system")}>
+ System
+
+
+
+ )
+}
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx
new file mode 100644
index 0000000..30d1451
--- /dev/null
+++ b/src/components/theme-provider.tsx
@@ -0,0 +1,9 @@
+"use client"
+
+import { ThemeProvider as NextThemesProvider } from "next-themes"
+import type { ThemeProviderProps } from "next-themes/dist/types"
+import * as React from "react"
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
diff --git a/src/components/todolist/index.tsx b/src/components/todolist/index.tsx
index bdaec5e..ec4d0fd 100644
--- a/src/components/todolist/index.tsx
+++ b/src/components/todolist/index.tsx
@@ -1,107 +1,170 @@
-import type { ChangeEvent, FC, MouseEventHandler } from "react";
-import { memo, useState } from "react";
-
-import type { Task, Todolist as TodolistType } from "../../services/todolists";
-import { Button } from "../button";
-import { Input } from "../input";
+import {
+ type ChangeEvent,
+ type FC,
+ type FormEvent,
+ memo,
+ useCallback,
+ useMemo,
+ useState,
+} from "react"
import {
+ Card,
+ CardContent,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card"
+import { Plus, Trash } from "lucide-react"
+import type {
+ Task,
+ Todolist as TodolistType,
+} from "../../services/todolist-api/todolists"
+import { Button } from "../ui/button"
+import { Input } from "../ui/input"
+
+import { Checkbox } from "@/components/ui/checkbox"
+import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
+import {
+ TaskStatus,
useCreateTaskMutation,
useDeleteTaskMutation,
useDeleteTodolistMutation,
useGetTasksQuery,
useUpdateTaskMutation,
-} from "@/services";
+} from "@/services"
type Props = {
- todolist: TodolistType;
-};
+ todolist: TodolistType
+}
-type Filter = "all" | "active" | "completed";
+type Filter = "all" | "active" | "completed"
export const Todolist: FC = memo(({ todolist }) => {
- const { data: tasks, isLoading } = useGetTasksQuery(todolist.id);
- const { mutate: putTask } = useUpdateTaskMutation();
- const { mutate: deleteTask } = useDeleteTaskMutation();
- const { mutate: deleteTodolist } = useDeleteTodolistMutation();
- const { mutate: createTask } = useCreateTaskMutation();
- const [newTaskTitle, setNewTaskTitle] = useState("");
- const [filter, setFilter] = useState("all");
+ const { data: tasks, isLoading } = useGetTasksQuery(todolist.id)
+ const { mutate: updateTask } = useUpdateTaskMutation()
+ const { mutate: deleteTask } = useDeleteTaskMutation()
+ const { mutate: deleteTodolist } = useDeleteTodolistMutation()
+ const { mutate: createTask } = useCreateTaskMutation()
+ const [newTaskTitle, setNewTaskTitle] = useState("")
+ const [filter, setFilter] = useState("all")
- const handleChangeStatus = (todolistId: string, task: Task) => {
- const newTask = { ...task, status: task.status === 0 ? 2 : 0 };
+ const handleChangeStatus = useCallback(
+ (todolistId: string, task: Task) => {
+ updateTask({
+ todolistId,
+ id: task.id,
+ status:
+ task.status === TaskStatus.DONE ? TaskStatus.OPEN : TaskStatus.DONE,
+ })
+ },
+ [updateTask],
+ )
+
+ const handleDeleteTask = useCallback(
+ (todolistId: string, taskId: string) => {
+ deleteTask({ todolistId, taskId })
+ },
+ [deleteTask],
+ )
- putTask({ todolistId, task: newTask });
- };
- const handleDeleteTask = (todolistId: string, taskId: string) => {
- deleteTask({ todolistId, taskId });
- };
const handleDeleteTodolist = (todolistId: string) => {
- deleteTodolist({ todolistId });
- };
- const handleAddTask = () => {
- createTask({ todolistId: todolist.id, title: newTaskTitle });
- setNewTaskTitle("");
- };
+ deleteTodolist({ todolistId })
+ }
+
+ const handleAddTask = (e: FormEvent) => {
+ e.preventDefault()
+ createTask({ todolistId: todolist.id, title: newTaskTitle })
+ setNewTaskTitle("")
+ }
+
const handleNewTaskTitleChange = (e: ChangeEvent) => {
- setNewTaskTitle(e.target.value);
- };
+ setNewTaskTitle(e.target.value)
+ }
- const handleFilterChange: MouseEventHandler = (e) => {
- setFilter(e.currentTarget.value as Filter);
- };
+ const handleFilterChange = (value: string) => {
+ setFilter(value as Filter)
+ }
- if (isLoading) return loading...
;
- const filteredTasks = tasks?.items?.filter((task) => {
+ const filteredTasks = tasks?.filter((task) => {
switch (filter) {
case "active":
- return task.status === 0;
+ return task.status === TaskStatus.OPEN
case "completed":
- return task.status === 2;
+ return task.status === TaskStatus.DONE
default:
- return true;
+ return true
}
- });
+ })
+
+ const renderTasks = useMemo(() => {
+ return filteredTasks?.map((task) => {
+ return (
+
+
+
+
+ )
+ })
+ }, [filteredTasks, todolist.id, handleChangeStatus, handleDeleteTask])
return (
-
-
-
{todolist.title}
-
+
+
+
+ {todolist.title}
+
+
+
+
+ {isLoading ? null : renderTasks}
+
-
-
-
-
- {filteredTasks?.map((task) => (
-
-
handleChangeStatus(todolist.id, task)}
- type={"checkbox"}
- checked={[0, 1, 3].includes(task.status)}
- />
-
{task.title}
-
-
- ))}
-
-
-
-
-
-
- );
-});
+
+
+
+ ALL
+
+
+ TODO
+
+
+ DONE
+
+
+
+
+ )
+})
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..1620e13
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import { Slot } from "@radix-ui/react-slot"
+import { type VariantProps, cva } from "class-variance-authority"
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10 shrink-0",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes
,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ },
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
new file mode 100644
index 0000000..0a9e625
--- /dev/null
+++ b/src/components/ui/card.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..ddbdd01
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,28 @@
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..5ac50f0
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,198 @@
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/src/components/ui/input/index.tsx b/src/components/ui/input/index.tsx
new file mode 100644
index 0000000..1086b7e
--- /dev/null
+++ b/src/components/ui/input/index.tsx
@@ -0,0 +1,35 @@
+import { Label } from "@/components/ui/label"
+import { cn } from "@/helpers"
+import * as React from "react"
+import { useId } from "react"
+
+export interface InputProps
+ extends React.InputHTMLAttributes {
+ label?: string
+}
+
+const Input = React.forwardRef(
+ ({ className, type, label, id, ...props }, ref) => {
+ const generatedId = useId()
+ const idToUse = id || generatedId
+
+ return (
+
+ {label && }
+
+
+ )
+ },
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 0000000..683faa7
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,24 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/src/components/loader/index.tsx b/src/components/ui/loader/index.tsx
similarity index 95%
rename from src/components/loader/index.tsx
rename to src/components/ui/loader/index.tsx
index 0e6b3e1..b2e8265 100644
--- a/src/components/loader/index.tsx
+++ b/src/components/ui/loader/index.tsx
@@ -3,5 +3,5 @@ export const Loader = () => {
- );
-};
+ )
+}
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..01b8b6d
--- /dev/null
+++ b/src/components/ui/skeleton.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({
+ className,
+ ...props
+}: React.HTMLAttributes) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/src/components/ui/toggle-group.tsx b/src/components/ui/toggle-group.tsx
new file mode 100644
index 0000000..8bfe044
--- /dev/null
+++ b/src/components/ui/toggle-group.tsx
@@ -0,0 +1,59 @@
+import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
+import type { VariantProps } from "class-variance-authority"
+import * as React from "react"
+
+import { toggleVariants } from "@/components/ui/toggle"
+import { cn } from "@/lib/utils"
+
+const ToggleGroupContext = React.createContext<
+ VariantProps
+>({
+ size: "default",
+ variant: "default",
+})
+
+const ToggleGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, size, children, ...props }, ref) => (
+
+
+ {children}
+
+
+))
+
+ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
+
+const ToggleGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, children, variant, size, ...props }, ref) => {
+ const context = React.useContext(ToggleGroupContext)
+
+ return (
+
+ {children}
+
+ )
+})
+
+ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
+
+export { ToggleGroup, ToggleGroupItem }
diff --git a/src/components/ui/toggle.tsx b/src/components/ui/toggle.tsx
new file mode 100644
index 0000000..9ecac28
--- /dev/null
+++ b/src/components/ui/toggle.tsx
@@ -0,0 +1,43 @@
+import * as React from "react"
+import * as TogglePrimitive from "@radix-ui/react-toggle"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const toggleVariants = cva(
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline:
+ "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
+ },
+ size: {
+ default: "h-10 px-3",
+ sm: "h-9 px-2.5",
+ lg: "h-11 px-5",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const Toggle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, size, ...props }, ref) => (
+
+))
+
+Toggle.displayName = TogglePrimitive.Root.displayName
+
+export { Toggle, toggleVariants }
diff --git a/src/constants/index.ts b/src/constants/index.ts
index bbe7f52..7b27518 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -1,3 +1,3 @@
-export * from "./routes";
-export * from "./query-keys";
-export * from "./result-code";
+export * from "./routes"
+export * from "./query-keys"
+export * from "./result-code"
diff --git a/src/constants/query-keys/index.ts b/src/constants/query-keys/index.ts
index 417b25b..6e9343c 100644
--- a/src/constants/query-keys/index.ts
+++ b/src/constants/query-keys/index.ts
@@ -2,4 +2,4 @@ export const QUERY_KEYS = {
TODOLISTS: "todolists",
TASKS: "tasks",
ME: "me",
-} as const;
+} as const
diff --git a/src/constants/routes/index.ts b/src/constants/routes/index.ts
index 956fa1a..9f5ae9c 100644
--- a/src/constants/routes/index.ts
+++ b/src/constants/routes/index.ts
@@ -1,4 +1,4 @@
export const ROUTES = {
HOME: "/",
LOGIN: "/login",
-} as const;
+} as const
diff --git a/src/env/client.mjs b/src/env/client.mjs
index 107c6d3..5d17abd 100644
--- a/src/env/client.mjs
+++ b/src/env/client.mjs
@@ -1,35 +1,35 @@
// @ts-check
-import { clientEnv, clientSchema } from "./schema.mjs";
+import { clientEnv, clientSchema } from "./schema.mjs"
-const _clientEnv = clientSchema.safeParse(clientEnv);
+const _clientEnv = clientSchema.safeParse(clientEnv)
export const formatErrors = (
/** @type {import('zod').ZodFormattedError