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 index 827672f..3b5fafa 100644 --- a/biome.json +++ b/biome.json @@ -9,5 +9,10 @@ "recommended": true } }, + "javascript": { + "formatter": { + "semicolons": "asNeeded" + } + }, "formatter": { "indentStyle": "space", "indentWidth": 2 } } diff --git a/bun.lockb b/bun.lockb index 774c8de..4e90f88 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/src/components/auth-redirect/index.tsx b/src/components/auth-redirect/index.tsx index 8b03e6c..dd5f54a 100644 --- a/src/components/auth-redirect/index.tsx +++ b/src/components/auth-redirect/index.tsx @@ -1,31 +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 "../loader" -import { useMeQuery } from "@/services"; +import { useMeQuery } from "@/services" export const AuthRedirect: FC<{ children: ReactNode }> = ({ children }) => { - const router = useRouter(); - const { data: user, isLoading } = useMeQuery(); + const router = useRouter() + const { data: user, isLoading } = useMeQuery() const isAuthPage = - router.pathname === "/login" || router.pathname === "/sign-up"; + router.pathname === "/login" || router.pathname === "/sign-up" useEffect(() => { if (!isLoading && !user && !isAuthPage) { - router.push("/login"); + router.push("/login") } - }, [user, 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 index 2f48aeb..6088487 100644 --- a/src/components/button/index.tsx +++ b/src/components/button/index.tsx @@ -1,10 +1,10 @@ -import type { ComponentPropsWithoutRef, FC } from "react"; +import type { ComponentPropsWithoutRef, FC } from "react" -import { cn } from "@/helpers"; +import { cn } from "@/helpers" type Props = ComponentPropsWithoutRef<"button"> & { - variant?: "primary" | "outlined" | "icon"; -}; + variant?: "primary" | "outlined" | "icon" +} export const Button: FC = ({ className, @@ -18,9 +18,9 @@ export const Button: FC = ({ variant === "outlined" && "border-sky-700 bg-inherit text-sky-700", variant === "icon" && "h-6 w-6 shrink-0 border-none bg-inherit p-0 text-sky-700 hover:bg-slate-100 hover:text-sky-800", - className + className, )} {...rest} /> - ); -}; + ) +} diff --git a/src/components/fullscreen-loader/index.tsx b/src/components/fullscreen-loader/index.tsx index c6a6a64..0f7911a 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 "../loader" export const FullscreenLoader = () => { return (
- ); -}; + ) +} diff --git a/src/components/index.ts b/src/components/index.ts index bb544b5..7775d5a 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 "./button" +export * from "./input" +export * from "./loader" +export * from "./fullscreen-loader" diff --git a/src/components/input/index.tsx b/src/components/input/index.tsx index 4d55992..4a123cc 100644 --- a/src/components/input/index.tsx +++ b/src/components/input/index.tsx @@ -1,9 +1,9 @@ -import type { DetailedHTMLProps, FC, InputHTMLAttributes } from "react"; +import type { DetailedHTMLProps, FC, InputHTMLAttributes } from "react" type Props = DetailedHTMLProps< InputHTMLAttributes, HTMLInputElement ->; +> export const Input: FC = ({ className, ...rest }) => { return ( @@ -11,5 +11,5 @@ export const Input: FC = ({ className, ...rest }) => { className={`w-full rounded-md border border-gray-300 px-4 py-2 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-600 ${className}`} {...rest} /> - ); -}; + ) +} diff --git a/src/components/loader/index.tsx b/src/components/loader/index.tsx index 0e6b3e1..b2e8265 100644 --- a/src/components/loader/index.tsx +++ b/src/components/loader/index.tsx @@ -3,5 +3,5 @@ export const Loader = () => {
- ); -}; + ) +} diff --git a/src/components/todolist/index.tsx b/src/components/todolist/index.tsx index 4953708..5bf07bd 100644 --- a/src/components/todolist/index.tsx +++ b/src/components/todolist/index.tsx @@ -1,19 +1,19 @@ -import type { ChangeEvent, FC, FormEvent, MouseEventHandler } from "react"; -import { memo, useState } from "react"; +import type { ChangeEvent, FC, FormEvent } from "react" +import { memo, useState } from "react" -import { Trash, Plus } from "lucide-react"; +import { Plus, Trash } from "lucide-react" import type { Task, Todolist as TodolistType, -} from "../../services/todolist-api/todolists"; -import { Button } from "../button"; -import { Input } from "../input"; +} from "../../services/todolist-api/todolists" +import { Button } from "../button" +import { Input } from "../input" import { ToggleGroup, ToggleGroupItem, -} from "@/components/toggle-group/toggle-group"; +} from "@/components/toggle-group/toggle-group" import { TaskStatus, useCreateTaskMutation, @@ -21,62 +21,62 @@ import { 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: putTask } = 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 = { ...task, status: task.status === TaskStatus.DONE ? TaskStatus.OPEN : TaskStatus.DONE, - }; + } - putTask({ todolistId, task: newTask }); - }; + putTask({ todolistId, task: newTask }) + } const handleDeleteTask = (todolistId: string, taskId: string) => { - deleteTask({ todolistId, taskId }); - }; + deleteTask({ todolistId, taskId }) + } const handleDeleteTodolist = (todolistId: string) => { - deleteTodolist({ todolistId }); - }; + deleteTodolist({ todolistId }) + } const handleAddTask = (e: FormEvent) => { - e.preventDefault(); - createTask({ todolistId: todolist.id, title: newTaskTitle }); - setNewTaskTitle(""); - }; + e.preventDefault() + createTask({ todolistId: todolist.id, title: newTaskTitle }) + setNewTaskTitle("") + } const handleNewTaskTitleChange = (e: ChangeEvent) => { - setNewTaskTitle(e.target.value); - }; + setNewTaskTitle(e.target.value) + } const handleFilterChange = (value: string) => { - setFilter(value as Filter); - }; + setFilter(value as Filter) + } - if (isLoading) return
loading...
; + if (isLoading) return
loading...
const filteredTasks = tasks?.filter((task) => { switch (filter) { case "active": - return task.status === TaskStatus.OPEN; + return task.status === TaskStatus.OPEN case "completed": - return task.status === TaskStatus.DONE; + return task.status === TaskStatus.DONE default: - return true; + return true } - }); + }) return (
= memo(({ todolist }) => {
- ); + ) })}
= memo(({ todolist }) => {
- ); -}); + ) +}) diff --git a/src/components/toggle-group/toggle-group.tsx b/src/components/toggle-group/toggle-group.tsx index 21339d1..2d1acc9 100644 --- a/src/components/toggle-group/toggle-group.tsx +++ b/src/components/toggle-group/toggle-group.tsx @@ -1,12 +1,12 @@ -"use client"; +"use client" -import * as React from "react"; +import * as React from "react" -import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"; -import type { VariantProps } from "class-variance-authority"; -import { cva } from "class-variance-authority"; +import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group" +import type { VariantProps } from "class-variance-authority" +import { cva } from "class-variance-authority" -import { cn } from "@/helpers"; +import { cn } from "@/helpers" const toggleVariants = cva( "inline-flex border border-sky-700 items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-sky-700 data-[state=on]:text-white", @@ -27,14 +27,14 @@ const toggleVariants = cva( variant: "default", size: "default", }, - } -); + }, +) const ToggleGroupContext = React.createContext< VariantProps >({ size: "default", variant: "default", -}); +}) const ToggleGroup = React.forwardRef< React.ElementRef, @@ -50,16 +50,16 @@ const ToggleGroup = React.forwardRef< {children} -)); +)) -ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName; +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); + const context = React.useContext(ToggleGroupContext) return ( {children} - ); -}); + ) +}) -ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName; +ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName -export { ToggleGroup, ToggleGroupItem }; +export { ToggleGroup, ToggleGroupItem } 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,string>} */ - errors + errors, ) => Object.entries(errors) .map(([name, value]) => { if (value && "_errors" in value) - return `${name}: ${value._errors.join(", ")}\n`; + return `${name}: ${value._errors.join(", ")}\n` }) - .filter(Boolean); + .filter(Boolean) if (!_clientEnv.success) { console.error( "❌ Invalid environment variables:\n", - ...formatErrors(_clientEnv.error.format()) - ); - throw new Error("Invalid environment variables"); + ...formatErrors(_clientEnv.error.format()), + ) + throw new Error("Invalid environment variables") } -for (let key of Object.keys(_clientEnv.data)) { +for (const key of Object.keys(_clientEnv.data)) { if (!key.startsWith("NEXT_PUBLIC_")) { console.warn( - `❌ Invalid public environment variable name: ${key}. It must begin with 'NEXT_PUBLIC_'` - ); + `❌ Invalid public environment variable name: ${key}. It must begin with 'NEXT_PUBLIC_'`, + ) - throw new Error("Invalid public environment variable name"); + throw new Error("Invalid public environment variable name") } } -export const env = _clientEnv.data; +export const env = _clientEnv.data diff --git a/src/env/schema.mjs b/src/env/schema.mjs index c621d13..6d4eef8 100644 --- a/src/env/schema.mjs +++ b/src/env/schema.mjs @@ -1,5 +1,5 @@ // @ts-check -import { z } from "zod"; +import { z } from "zod" /** * Specify your server-side environment variables schema here. @@ -7,7 +7,7 @@ import { z } from "zod"; */ export const serverSchema = z.object({ NODE_ENV: z.enum(["development", "test", "production"]), -}); +}) /** * Specify your client-side environment variables schema here. @@ -16,7 +16,7 @@ export const serverSchema = z.object({ */ export const clientSchema = z.object({ // NEXT_PUBLIC_CLIENTVAR: z.string(), -}); +}) /** * You can't destruct `process.env` as a regular object, so you have to do @@ -26,4 +26,4 @@ export const clientSchema = z.object({ */ export const clientEnv = { // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, -}; +} diff --git a/src/env/server.mjs b/src/env/server.mjs index 4937bb5..03b6fe0 100644 --- a/src/env/server.mjs +++ b/src/env/server.mjs @@ -3,25 +3,25 @@ * This file is included in `/next.config.mjs` which ensures the app isn't built with invalid env vars. * It has to be a `.mjs`-file to be imported there. */ -import { serverSchema } from "./schema.mjs"; -import { env as clientEnv, formatErrors } from "./client.mjs"; +import { serverSchema } from "./schema.mjs" +import { env as clientEnv, formatErrors } from "./client.mjs" -const _serverEnv = serverSchema.safeParse(process.env); +const _serverEnv = serverSchema.safeParse(process.env) if (!_serverEnv.success) { console.error( "❌ Invalid environment variables:\n", - ...formatErrors(_serverEnv.error.format()) - ); - throw new Error("Invalid environment variables"); + ...formatErrors(_serverEnv.error.format()), + ) + throw new Error("Invalid environment variables") } -for (let key of Object.keys(_serverEnv.data)) { +for (const key of Object.keys(_serverEnv.data)) { if (key.startsWith("NEXT_PUBLIC_")) { - console.warn("❌ You are exposing a server-side env-variable:", key); + console.warn("❌ You are exposing a server-side env-variable:", key) - throw new Error("You are exposing a server-side env-variable"); + throw new Error("You are exposing a server-side env-variable") } } -export const env = { ..._serverEnv.data, ...clientEnv }; +export const env = { ..._serverEnv.data, ...clientEnv } diff --git a/src/helpers/api-response/index.ts b/src/helpers/api-response/index.ts index c30b670..bff2302 100644 --- a/src/helpers/api-response/index.ts +++ b/src/helpers/api-response/index.ts @@ -1,17 +1,17 @@ -import type { ResultCode } from "@/constants"; +import type { ResultCode } from "@/constants" type ApiResponseSuccess = { - data: T; - messages?: never; - fieldsErrors?: never; - resultCode: ResultCode.Success; -}; + data: T + messages?: never + fieldsErrors?: never + resultCode: ResultCode.Success +} type ApiResponseError = { - data: T; - messages: string[]; - fieldsErrors?: string[]; - resultCode: ResultCode.InvalidRequest | ResultCode.InvalidRequestWithCaptcha; -}; + data: T + messages: string[] + fieldsErrors?: string[] + resultCode: ResultCode.InvalidRequest | ResultCode.InvalidRequestWithCaptcha +} -export type ApiResponse = ApiResponseSuccess | ApiResponseError; +export type ApiResponse = ApiResponseSuccess | ApiResponseError diff --git a/src/helpers/classnames/classnames.ts b/src/helpers/classnames/classnames.ts index a5ef193..bd0c391 100644 --- a/src/helpers/classnames/classnames.ts +++ b/src/helpers/classnames/classnames.ts @@ -1,6 +1,6 @@ -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)) } diff --git a/src/helpers/classnames/index.ts b/src/helpers/classnames/index.ts index 15c0006..8cd8f33 100644 --- a/src/helpers/classnames/index.ts +++ b/src/helpers/classnames/index.ts @@ -1 +1 @@ -export * from "./classnames"; +export * from "./classnames" diff --git a/src/helpers/handle-error/index.ts b/src/helpers/handle-error/index.ts index 2dea81f..7e0e43b 100644 --- a/src/helpers/handle-error/index.ts +++ b/src/helpers/handle-error/index.ts @@ -1,4 +1,4 @@ -import type { ApiResponse } from "@/helpers"; +import type { ApiResponse } from "@/helpers" export const handleError = (data: ApiResponse): ApiResponse => { // if (data.resultCode !== 0) { @@ -7,5 +7,5 @@ export const handleError = (data: ApiResponse): ApiResponse => { // throw new Error(error); // } - return data; -}; + return data +} diff --git a/src/helpers/index.ts b/src/helpers/index.ts index daeb2d4..b7ea556 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,4 +1,4 @@ -export * from "./handle-error"; -export * from "./no-refetch"; -export * from "./api-response"; -export * from "./classnames"; +export * from "./handle-error" +export * from "./no-refetch" +export * from "./api-response" +export * from "./classnames" diff --git a/src/helpers/no-refetch/index.ts b/src/helpers/no-refetch/index.ts index 904079a..9f267c9 100644 --- a/src/helpers/no-refetch/index.ts +++ b/src/helpers/no-refetch/index.ts @@ -5,4 +5,4 @@ export const noRefetch = { refetchOnMount: false, refetchIntervalInBackground: false, retry: false, -} as const; +} as const diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index d14b253..20aa50d 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,11 +1,11 @@ -import "../styles/globals.css"; +import "../styles/globals.css" -import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; -import { type AppType } from "next/dist/shared/lib/utils"; +import { QueryClientProvider, QueryClient } from "@tanstack/react-query" +import type { AppType } from "next/dist/shared/lib/utils" -import { AuthRedirect } from "@/components"; +import { AuthRedirect } from "@/components" -const queryClient = new QueryClient(); +const queryClient = new QueryClient() const MyApp: AppType = ({ Component, pageProps }) => { return ( @@ -14,7 +14,7 @@ const MyApp: AppType = ({ Component, pageProps }) => { - ); -}; + ) +} -export default MyApp; +export default MyApp diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 0fb3b31..d1a5542 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,39 +1,38 @@ -import type { ChangeEvent, FormEvent } from "react"; -import { useState } from "react"; +import type { ChangeEvent, FormEvent } from "react" +import { useState } from "react" -import { type NextPage } from "next"; -import Head from "next/head"; +import type { NextPage } from "next" +import Head from "next/head" -import { Todolist, Button, FullscreenLoader, Input } from "@/components"; +import { Todolist, Button, FullscreenLoader, Input } from "@/components" import { useCreateTodolistMutation, useLogoutMutation, useTodolistsQuery, -} from "@/services"; +} from "@/services" const Home: NextPage = () => { - const [newTodolistTitle, setNewTodolistTitle] = useState(""); - const { mutate: logout } = useLogoutMutation(); - const { data: todolists, isLoading: isTodolistsLoading } = - useTodolistsQuery(); + const [newTodolistTitle, setNewTodolistTitle] = useState("") + const { mutate: logout } = useLogoutMutation() + const { data: todolists, isLoading: isTodolistsLoading } = useTodolistsQuery() const handleLogout = () => { - logout(); - }; + logout() + } - const { mutate: createTodolist } = useCreateTodolistMutation(); + const { mutate: createTodolist } = useCreateTodolistMutation() const handleAddTodolist = (e: FormEvent) => { - e.preventDefault(); - createTodolist({ title: newTodolistTitle }); - setNewTodolistTitle(""); - }; + e.preventDefault() + createTodolist({ title: newTodolistTitle }) + setNewTodolistTitle("") + } const handleNewTodolistTitleChange = (e: ChangeEvent) => { - setNewTodolistTitle(e.target.value); - }; + setNewTodolistTitle(e.target.value) + } - if (isTodolistsLoading) return ; + if (isTodolistsLoading) return return ( <> @@ -59,12 +58,12 @@ const Home: NextPage = () => {
{todolists?.map((todolist) => { - return ; + return })}
- ); -}; + ) +} -export default Home; +export default Home diff --git a/src/pages/login.tsx b/src/pages/login.tsx index 629bdac..5f8c330 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -1,37 +1,37 @@ -import type { ChangeEvent } from "react"; -import React, { useState } from "react"; +import type { ChangeEvent } from "react" +import React, { useState } from "react" -import type { NextPage } from "next"; +import type { NextPage } from "next" -import { Button, Input } from "@/components"; -import { useLoginMutation } from "@/services"; +import { Button, Input } from "@/components" +import { useLoginMutation } from "@/services" const Login: NextPage = () => { - const { mutate: login } = useLoginMutation(); + const { mutate: login } = useLoginMutation() - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [remember, setRemember] = useState(true); + const [email, setEmail] = useState("") + const [password, setPassword] = useState("") + const [remember, setRemember] = useState(true) const handlePasswordChange = (e: ChangeEvent) => { - setPassword(e.target.value); - }; + setPassword(e.target.value) + } const handleEmailChange = (e: ChangeEvent) => { - setEmail(e.target.value); - }; + setEmail(e.target.value) + } const handleRememberChange = (e: ChangeEvent) => { - setRemember(e.target.checked); - }; + setRemember(e.target.checked) + } const handleSubmit = () => { login({ email, password, rememberMe: remember, - }); - }; + }) + } return (
@@ -63,7 +63,7 @@ const Login: NextPage = () => {
- ); -}; + ) +} -export default Login; +export default Login diff --git a/src/pages/sign-up.tsx b/src/pages/sign-up.tsx index 85fb9aa..6cfee57 100644 --- a/src/pages/sign-up.tsx +++ b/src/pages/sign-up.tsx @@ -1,21 +1,21 @@ -import type { FormEvent } from "react"; -import React from "react"; +import type { FormEvent } from "react" +import React from "react" -import type { NextPage } from "next"; +import type { NextPage } from "next" -import { Button, Input } from "@/components"; -import { useSignUpMutation } from "@/services"; +import { Button, Input } from "@/components" +import { useSignUpMutation } from "@/services" const Login: NextPage = () => { - const { mutate: signUp } = useSignUpMutation(); + const { mutate: signUp } = useSignUpMutation() const handleSubmit = (e: FormEvent) => { - e.preventDefault(); - const formData = new FormData(e.currentTarget); + e.preventDefault() + const formData = new FormData(e.currentTarget) - const values = Object.fromEntries(formData) as any; - signUp(values); - }; + const values = Object.fromEntries(formData) as any + signUp(values) + } return (
@@ -43,7 +43,7 @@ const Login: NextPage = () => {
- ); -}; + ) +} -export default Login; +export default Login diff --git a/src/services/index.ts b/src/services/index.ts index 8847ade..55f5072 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,2 +1,2 @@ -export * from "./todolist-api/auth"; -export * from "./todolist-api/todolists"; +export * from "./todolist-api/auth" +export * from "./todolist-api/todolists" diff --git a/src/services/todolist-api/auth/auth.api.ts b/src/services/todolist-api/auth/auth.api.ts index 7c707f3..b87c4f6 100644 --- a/src/services/todolist-api/auth/auth.api.ts +++ b/src/services/todolist-api/auth/auth.api.ts @@ -4,37 +4,37 @@ import type { MeResponse, PostLoginArgs, PostSignUpArgs, -} from "../todolists"; +} from "../todolists" -import { handleError } from "@/helpers"; -import { todolistApiInstance } from "@/services/todolist-api/todolist-api.instance"; +import { handleError } from "@/helpers" +import { todolistApiInstance } from "@/services/todolist-api/todolist-api.instance" export const AuthApi = { async login(args: PostLoginArgs) { const res = await todolistApiInstance.post( "/auth/login", args, - ); + ) - localStorage.setItem("accessToken", res.data.accessToken); - localStorage.setItem("refreshToken", res.data.refreshToken); + localStorage.setItem("accessToken", res.data.accessToken) + localStorage.setItem("refreshToken", res.data.refreshToken) - return res.data; + return res.data }, async signUp(args: PostSignUpArgs) { - const res = await todolistApiInstance.post("/auth/sign-up", args); + const res = await todolistApiInstance.post("/auth/sign-up", args) - return res.data; + return res.data }, async logout() { - const res = await todolistApiInstance.delete("/auth/login"); + const res = await todolistApiInstance.delete("/auth/login") - return handleError(res.data); + return handleError(res.data) }, async me() { - const res = await todolistApiInstance.get("/auth/me"); + const res = await todolistApiInstance.get("/auth/me") - return handleError(res.data); + return handleError(res.data) }, -}; +} diff --git a/src/services/todolist-api/auth/auth.hooks.ts b/src/services/todolist-api/auth/auth.hooks.ts index cab23ad..96abb81 100644 --- a/src/services/todolist-api/auth/auth.hooks.ts +++ b/src/services/todolist-api/auth/auth.hooks.ts @@ -1,46 +1,46 @@ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useRouter } from "next/router"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { useRouter } from "next/router" -import { QUERY_KEYS, ROUTES } from "@/constants"; -import { noRefetch } from "@/helpers"; -import { AuthApi } from "@/services/todolist-api/auth/auth.api"; +import { QUERY_KEYS, ROUTES } from "@/constants" +import { noRefetch } from "@/helpers" +import { AuthApi } from "@/services/todolist-api/auth/auth.api" export const useMeQuery = () => { return useQuery({ queryFn: AuthApi.me, queryKey: [QUERY_KEYS.ME], ...noRefetch, - }); -}; + }) +} export const useLoginMutation = () => { - const router = useRouter(); - const queryClient = useQueryClient(); + const router = useRouter() + const queryClient = useQueryClient() return useMutation({ mutationFn: AuthApi.login, onSuccess: async () => { - await queryClient.invalidateQueries([QUERY_KEYS.ME]); - await router.push(ROUTES.HOME); + await queryClient.invalidateQueries([QUERY_KEYS.ME]) + await router.push(ROUTES.HOME) }, - }); -}; + }) +} export const useLogoutMutation = () => { - const queryClient = useQueryClient(); - const router = useRouter(); + const queryClient = useQueryClient() + const router = useRouter() return useMutation({ mutationFn: AuthApi.logout, onSuccess: async () => { - await queryClient.invalidateQueries([QUERY_KEYS.ME]); - await router.push(ROUTES.LOGIN); + await queryClient.invalidateQueries([QUERY_KEYS.ME]) + await router.push(ROUTES.LOGIN) }, - }); -}; + }) +} export const useSignUpMutation = () => { return useMutation({ mutationFn: AuthApi.signUp, - }); -}; + }) +} diff --git a/src/services/todolist-api/auth/index.ts b/src/services/todolist-api/auth/index.ts index f13aaf0..f8be71d 100644 --- a/src/services/todolist-api/auth/index.ts +++ b/src/services/todolist-api/auth/index.ts @@ -1,2 +1,2 @@ -export * from "./auth.api"; -export * from "./auth.hooks"; +export * from "./auth.api" +export * from "./auth.hooks" diff --git a/src/services/todolist-api/todolist-api.instance.ts b/src/services/todolist-api/todolist-api.instance.ts index 2d701ff..bfa4615 100644 --- a/src/services/todolist-api/todolist-api.instance.ts +++ b/src/services/todolist-api/todolist-api.instance.ts @@ -1,19 +1,19 @@ -import { Mutex } from "async-mutex"; -import axios from "axios"; -import router from "next/router"; +import { Mutex } from "async-mutex" +import axios from "axios" +import router from "next/router" -const mutex = new Mutex(); +const mutex = new Mutex() -let refreshedAt: number | null = null; +let refreshedAt: number | null = null export const todolistApiInstance = axios.create({ baseURL: "http://localhost:3000", -}); +}) async function refreshAccessToken(): Promise { const res = await todolistApiInstance.post<{ - refreshToken: string; - accessToken: string; + refreshToken: string + accessToken: string }>( "/auth/refresh-token", {}, @@ -22,76 +22,76 @@ async function refreshAccessToken(): Promise { Authorization: `Bearer ${localStorage.getItem("refreshToken")}`, }, }, - ); + ) - localStorage.setItem("accessToken", res.data.accessToken); - localStorage.setItem("refreshToken", res.data.refreshToken); + localStorage.setItem("accessToken", res.data.accessToken) + localStorage.setItem("refreshToken", res.data.refreshToken) - return res.data.accessToken; + return res.data.accessToken } todolistApiInstance.interceptors.request.use( async (config) => { if (!config?.url?.includes("refresh-token")) { - await mutex.waitForUnlock(); + await mutex.waitForUnlock() } config.headers.Authorization = config.headers.Authorization ?? - `Bearer ${localStorage.getItem("accessToken")}`; + `Bearer ${localStorage.getItem("accessToken")}` - return config; + return config }, (error) => Promise.reject(error), -); +) todolistApiInstance.interceptors.response.use( (response) => response, async (error) => { if (error?.response?.request?.responseURL?.includes("refresh-token")) { - return; + return } - await mutex.waitForUnlock(); - const originalRequest = error.config; + await mutex.waitForUnlock() + const originalRequest = error.config // Check for a 401 response and if this request hasn't been retried yet if (error.response?.status === 401 && !originalRequest._retry) { if (!mutex.isLocked()) { - originalRequest._retry = true; + originalRequest._retry = true // Use a mutex to ensure that token refresh logic is not run multiple times in parallel - const release = await mutex.acquire(); + const release = await mutex.acquire() if (refreshedAt && refreshedAt + 60000 > new Date().getTime()) { // If the token has been refreshed within the last minute, use the refreshed token - originalRequest.headers.Authorization = `Bearer ${localStorage.getItem("accessToken")}`; - release(); + originalRequest.headers.Authorization = `Bearer ${localStorage.getItem("accessToken")}` + release() - return todolistApiInstance(originalRequest); + return todolistApiInstance(originalRequest) } - refreshedAt = new Date().getTime(); + refreshedAt = new Date().getTime() try { - const newAccessToken = await refreshAccessToken(); + const newAccessToken = await refreshAccessToken() - originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + originalRequest.headers.Authorization = `Bearer ${newAccessToken}` - return todolistApiInstance(originalRequest); // Retry the original request with the new token + return todolistApiInstance(originalRequest) // Retry the original request with the new token } catch (error) { - console.log(window.location.pathname); + console.log(window.location.pathname) if (!["/login", "/sign-up"].includes(window.location.pathname)) { - router.push("/login"); + router.push("/login") } } finally { - release(); + release() } } else { - await mutex.waitForUnlock(); - const newAccessToken = localStorage.getItem("accessToken"); + await mutex.waitForUnlock() + const newAccessToken = localStorage.getItem("accessToken") - originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + originalRequest.headers.Authorization = `Bearer ${newAccessToken}` - return todolistApiInstance(originalRequest); // Retry the original request with the new token + return todolistApiInstance(originalRequest) // Retry the original request with the new token } } - return Promise.reject(error); + return Promise.reject(error) }, -); +) diff --git a/src/services/todolist-api/todolists/index.ts b/src/services/todolist-api/todolists/index.ts index 4ee7e85..982e747 100644 --- a/src/services/todolist-api/todolists/index.ts +++ b/src/services/todolist-api/todolists/index.ts @@ -1,3 +1,3 @@ -export * from "./todolists.hooks"; -export * from "./todolists.api"; -export * from "./todolists.types"; +export * from "./todolists.hooks" +export * from "./todolists.api" +export * from "./todolists.types" diff --git a/src/services/todolist-api/todolists/todolists.api.ts b/src/services/todolist-api/todolists/todolists.api.ts index 70e630a..2010988 100644 --- a/src/services/todolist-api/todolists/todolists.api.ts +++ b/src/services/todolist-api/todolists/todolists.api.ts @@ -1,4 +1,4 @@ -import { handleError } from "@/helpers"; +import { handleError } from "@/helpers" import type { CreateTaskResponse, CreateTodolistResponse, @@ -8,14 +8,14 @@ import type { TasksResponse, Todolist, UpdateTaskResponse, -} from "@/services"; -import { todolistApiInstance } from "@/services/todolist-api/todolist-api.instance"; +} from "@/services" +import { todolistApiInstance } from "@/services/todolist-api/todolist-api.instance" export const TodolistAPI = { async getTodolists() { - const res = await todolistApiInstance.get("/todolists"); + const res = await todolistApiInstance.get("/todolists") - return res.data; + return res.data }, async createTodolist({ title }: { title: string }) { @@ -23,64 +23,64 @@ export const TodolistAPI = { "/todolists", { title, - } - ); + }, + ) - return handleError(res.data); + return handleError(res.data) }, async deleteTodolist({ todolistId }: { todolistId: string }) { const res = await todolistApiInstance.delete( - `/todolists/${todolistId}` - ); + `/todolists/${todolistId}`, + ) - return res.data; + return res.data }, async getTodolistTasks({ todolistId }: { todolistId: string }) { const res = await todolistApiInstance.get( - `/todolists/${todolistId}/tasks` - ); + `/todolists/${todolistId}/tasks`, + ) - return res.data; + return res.data }, async createTask({ todolistId, title, }: { - todolistId: string; - title: string; + todolistId: string + title: string }) { const res = await todolistApiInstance.post( `/todolists/${todolistId}/tasks`, - { title } - ); + { title }, + ) - return res.data; + return res.data }, async updateTask({ todolistId, task }: { todolistId: string; task: Task }) { - const { id, ...rest } = task; + const { id, ...rest } = task const res = await todolistApiInstance.patch( `/todolists/${todolistId}/tasks/${id}`, - rest - ); + rest, + ) - return res.data; + return res.data }, async deleteTask({ todolistId, taskId, }: { - todolistId: string; - taskId: string; + todolistId: string + taskId: string }) { const res = await todolistApiInstance.delete( - `/todolists/${todolistId}/tasks/${taskId}` - ); + `/todolists/${todolistId}/tasks/${taskId}`, + ) - return res.data; + return res.data }, -}; +} diff --git a/src/services/todolist-api/todolists/todolists.hooks.ts b/src/services/todolist-api/todolists/todolists.hooks.ts index ef77559..91637cc 100644 --- a/src/services/todolist-api/todolists/todolists.hooks.ts +++ b/src/services/todolist-api/todolists/todolists.hooks.ts @@ -1,80 +1,80 @@ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" -import { QUERY_KEYS } from "@/constants"; -import { noRefetch } from "@/helpers"; -import { TodolistAPI } from "@/services"; +import { QUERY_KEYS } from "@/constants" +import { noRefetch } from "@/helpers" +import { TodolistAPI } from "@/services" export const useTodolistsQuery = () => { return useQuery({ queryFn: TodolistAPI.getTodolists, queryKey: [QUERY_KEYS.TODOLISTS], ...noRefetch, - }); -}; + }) +} export const useCreateTodolistMutation = () => { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() return useMutation({ mutationFn: TodolistAPI.createTodolist, //todo: add onMutate onSuccess: async () => { - await queryClient.invalidateQueries([QUERY_KEYS.TODOLISTS]); + await queryClient.invalidateQueries([QUERY_KEYS.TODOLISTS]) }, - }); -}; + }) +} export const useDeleteTodolistMutation = () => { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() return useMutation({ mutationFn: TodolistAPI.deleteTodolist, onSuccess: () => { - queryClient.invalidateQueries([QUERY_KEYS.TODOLISTS]); + queryClient.invalidateQueries([QUERY_KEYS.TODOLISTS]) }, - }); -}; + }) +} export const useGetTasksQuery = (todolistId: string) => { return useQuery({ queryKey: [QUERY_KEYS.TASKS, todolistId], queryFn: () => TodolistAPI.getTodolistTasks({ todolistId }), - }); -}; + }) +} export const useCreateTaskMutation = () => { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() return useMutation({ mutationFn: TodolistAPI.createTask, onSuccess: (res) => { - const todolistId = res.todoListId; + const todolistId = res.todoListId - queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]); + queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]) }, - }); -}; + }) +} export const useUpdateTaskMutation = () => { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() return useMutation({ mutationFn: TodolistAPI.updateTask, onSuccess: async (_, { todolistId }) => { - await queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]); + await queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]) }, - }); -}; + }) +} export const useDeleteTaskMutation = () => { - const queryClient = useQueryClient(); + const queryClient = useQueryClient() return useMutation({ mutationFn: TodolistAPI.deleteTask, onSuccess: (_, variables) => { - const todolistId = variables.todolistId; + const todolistId = variables.todolistId - queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]); + queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]) }, - }); -}; + }) +} diff --git a/src/services/todolist-api/todolists/todolists.types.ts b/src/services/todolist-api/todolists/todolists.types.ts index 521d264..d7f3162 100644 --- a/src/services/todolist-api/todolists/todolists.types.ts +++ b/src/services/todolist-api/todolists/todolists.types.ts @@ -1,64 +1,64 @@ -import type { ApiResponse } from "@/helpers"; +import type { ApiResponse } from "@/helpers" -export type TasksResponse = Task[]; +export type TasksResponse = Task[] export type Task = { - order: string; - title: string; - description?: string | null; - deadline?: string | null; - status: TaskStatus; - priority: TaskPriority; - ownerId: string; - todoListId: string; - id: string; - createdAt: string; - updatedAt: string; -}; + order: string + title: string + description?: string | null + deadline?: string | null + status: TaskStatus + priority: TaskPriority + ownerId: string + todoListId: string + id: string + createdAt: string + updatedAt: string +} export type Todolist = { - id: string; - title: string; - addedDate: Date; - order: number; -}; + id: string + title: string + addedDate: Date + order: number +} export type PostLoginArgs = { - email: string; - password: string; - rememberMe: boolean; -}; + email: string + password: string + rememberMe: boolean +} export type PostSignUpArgs = { - email: string; - password: string; - username?: string; -}; + email: string + password: string + username?: string +} export type LoginResponseData = { - accessToken: string; - refreshToken: string; -}; + accessToken: string + refreshToken: string +} export type MeResponseData = { - id: number; - login: string; - email: string; -}; + id: number + login: string + email: string +} -export type LoginResponse = LoginResponseData; -export type LogoutResponse = ApiResponse; -export type MeResponse = ApiResponse; +export type LoginResponse = LoginResponseData +export type LogoutResponse = ApiResponse +export type MeResponse = ApiResponse -export type DeleteTodolistResponse = ApiResponse; -export type CreateTodolistResponse = ApiResponse; +export type DeleteTodolistResponse = ApiResponse +export type CreateTodolistResponse = ApiResponse export type CreateTodolistResponseData = { - item: Todolist; -}; + item: Todolist +} -export type CreateTaskResponse = Task; -export type DeleteTaskResponse = undefined; -export type UpdateTaskResponse = undefined; +export type CreateTaskResponse = Task +export type DeleteTaskResponse = undefined +export type UpdateTaskResponse = undefined export enum TaskStatus { OPEN = "OPEN",