mirror of
https://github.com/ershisan99/todolist_next.git
synced 2025-12-16 20:59:24 +00:00
lint and format
This commit is contained in:
6
.idea/jsLibraryMappings.xml
generated
Normal file
6
.idea/jsLibraryMappings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<includedPredefinedLibrary name="Node.js Core" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -9,5 +9,10 @@
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"semicolons": "asNeeded"
|
||||
}
|
||||
},
|
||||
"formatter": { "indentStyle": "space", "indentWidth": 2 }
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className={"h-screen"}>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
@@ -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<Props> = ({
|
||||
className,
|
||||
@@ -18,9 +18,9 @@ export const Button: FC<Props> = ({
|
||||
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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Loader } from "../loader";
|
||||
import { Loader } from "../loader"
|
||||
|
||||
export const FullscreenLoader = () => {
|
||||
return (
|
||||
<div className={"flex h-screen items-center justify-center"}>
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { DetailedHTMLProps, FC, InputHTMLAttributes } from "react";
|
||||
import type { DetailedHTMLProps, FC, InputHTMLAttributes } from "react"
|
||||
|
||||
type Props = DetailedHTMLProps<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
>;
|
||||
>
|
||||
|
||||
export const Input: FC<Props> = ({ className, ...rest }) => {
|
||||
return (
|
||||
@@ -11,5 +11,5 @@ export const Input: FC<Props> = ({ 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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ export const Loader = () => {
|
||||
<div className={"flex h-full w-full items-center justify-center"}>
|
||||
<span className="loader" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<Props> = 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<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
createTask({ todolistId: todolist.id, title: newTaskTitle });
|
||||
setNewTaskTitle("");
|
||||
};
|
||||
e.preventDefault()
|
||||
createTask({ todolistId: todolist.id, title: newTaskTitle })
|
||||
setNewTaskTitle("")
|
||||
}
|
||||
const handleNewTaskTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setNewTaskTitle(e.target.value);
|
||||
};
|
||||
setNewTaskTitle(e.target.value)
|
||||
}
|
||||
|
||||
const handleFilterChange = (value: string) => {
|
||||
setFilter(value as Filter);
|
||||
};
|
||||
setFilter(value as Filter)
|
||||
}
|
||||
|
||||
if (isLoading) return <div>loading...</div>;
|
||||
if (isLoading) return <div>loading...</div>
|
||||
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 (
|
||||
<div
|
||||
@@ -122,7 +122,7 @@ export const Todolist: FC<Props> = memo(({ todolist }) => {
|
||||
<Trash size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
})}
|
||||
<div className={"flex"}>
|
||||
<ToggleGroup
|
||||
@@ -143,5 +143,5 @@ export const Todolist: FC<Props> = memo(({ todolist }) => {
|
||||
</ToggleGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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<typeof toggleVariants>
|
||||
>({
|
||||
size: "default",
|
||||
variant: "default",
|
||||
});
|
||||
})
|
||||
|
||||
const ToggleGroup = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||
@@ -50,16 +50,16 @@ const ToggleGroup = React.forwardRef<
|
||||
{children}
|
||||
</ToggleGroupContext.Provider>
|
||||
</ToggleGroupPrimitive.Root>
|
||||
));
|
||||
))
|
||||
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
||||
|
||||
const ToggleGroupItem = React.forwardRef<
|
||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, children, variant, size, ...props }, ref) => {
|
||||
const context = React.useContext(ToggleGroupContext);
|
||||
const context = React.useContext(ToggleGroupContext)
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive.Item
|
||||
@@ -69,15 +69,15 @@ const ToggleGroupItem = React.forwardRef<
|
||||
variant: context.variant || variant,
|
||||
size: context.size || size,
|
||||
}),
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ToggleGroupPrimitive.Item>
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
|
||||
|
||||
export { ToggleGroup, ToggleGroupItem };
|
||||
export { ToggleGroup, ToggleGroupItem }
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,4 +2,4 @@ export const QUERY_KEYS = {
|
||||
TODOLISTS: "todolists",
|
||||
TASKS: "tasks",
|
||||
ME: "me",
|
||||
} as const;
|
||||
} as const
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const ROUTES = {
|
||||
HOME: "/",
|
||||
LOGIN: "/login",
|
||||
} as const;
|
||||
} as const
|
||||
|
||||
26
src/env/client.mjs
vendored
26
src/env/client.mjs
vendored
@@ -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<Map<string,string>,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
|
||||
|
||||
8
src/env/schema.mjs
vendored
8
src/env/schema.mjs
vendored
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
20
src/env/server.mjs
vendored
20
src/env/server.mjs
vendored
@@ -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 }
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { ResultCode } from "@/constants";
|
||||
import type { ResultCode } from "@/constants"
|
||||
|
||||
type ApiResponseSuccess<T> = {
|
||||
data: T;
|
||||
messages?: never;
|
||||
fieldsErrors?: never;
|
||||
resultCode: ResultCode.Success;
|
||||
};
|
||||
data: T
|
||||
messages?: never
|
||||
fieldsErrors?: never
|
||||
resultCode: ResultCode.Success
|
||||
}
|
||||
|
||||
type ApiResponseError<T> = {
|
||||
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<T> = ApiResponseSuccess<T> | ApiResponseError<T>;
|
||||
export type ApiResponse<T> = ApiResponseSuccess<T> | ApiResponseError<T>
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from "./classnames";
|
||||
export * from "./classnames"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse } from "@/helpers";
|
||||
import type { ApiResponse } from "@/helpers"
|
||||
|
||||
export const handleError = <T>(data: ApiResponse<T>): ApiResponse<T> => {
|
||||
// if (data.resultCode !== 0) {
|
||||
@@ -7,5 +7,5 @@ export const handleError = <T>(data: ApiResponse<T>): ApiResponse<T> => {
|
||||
// throw new Error(error);
|
||||
// }
|
||||
|
||||
return data;
|
||||
};
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -5,4 +5,4 @@ export const noRefetch = {
|
||||
refetchOnMount: false,
|
||||
refetchIntervalInBackground: false,
|
||||
retry: false,
|
||||
} as const;
|
||||
} as const
|
||||
|
||||
@@ -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 }) => {
|
||||
<Component {...pageProps} />
|
||||
</AuthRedirect>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
export default MyApp
|
||||
|
||||
@@ -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<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
createTodolist({ title: newTodolistTitle });
|
||||
setNewTodolistTitle("");
|
||||
};
|
||||
e.preventDefault()
|
||||
createTodolist({ title: newTodolistTitle })
|
||||
setNewTodolistTitle("")
|
||||
}
|
||||
|
||||
const handleNewTodolistTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setNewTodolistTitle(e.target.value);
|
||||
};
|
||||
setNewTodolistTitle(e.target.value)
|
||||
}
|
||||
|
||||
if (isTodolistsLoading) return <FullscreenLoader />;
|
||||
if (isTodolistsLoading) return <FullscreenLoader />
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -59,12 +58,12 @@ const Home: NextPage = () => {
|
||||
</form>
|
||||
<div className={"flex flex-wrap gap-4"}>
|
||||
{todolists?.map((todolist) => {
|
||||
return <Todolist todolist={todolist} key={todolist.id} />;
|
||||
return <Todolist todolist={todolist} key={todolist.id} />
|
||||
})}
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Home;
|
||||
export default Home
|
||||
|
||||
@@ -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<HTMLInputElement>) => {
|
||||
setPassword(e.target.value);
|
||||
};
|
||||
setPassword(e.target.value)
|
||||
}
|
||||
|
||||
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setEmail(e.target.value);
|
||||
};
|
||||
setEmail(e.target.value)
|
||||
}
|
||||
|
||||
const handleRememberChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setRemember(e.target.checked);
|
||||
};
|
||||
setRemember(e.target.checked)
|
||||
}
|
||||
|
||||
const handleSubmit = () => {
|
||||
login({
|
||||
email,
|
||||
password,
|
||||
rememberMe: remember,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={"flex h-screen items-center justify-center"}>
|
||||
@@ -63,7 +63,7 @@ const Login: NextPage = () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Login;
|
||||
export default Login
|
||||
|
||||
@@ -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<HTMLFormElement>) => {
|
||||
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 (
|
||||
<div className={"flex h-screen items-center justify-center"}>
|
||||
@@ -43,7 +43,7 @@ const Login: NextPage = () => {
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Login;
|
||||
export default Login
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./todolist-api/auth";
|
||||
export * from "./todolist-api/todolists";
|
||||
export * from "./todolist-api/auth"
|
||||
export * from "./todolist-api/todolists"
|
||||
|
||||
@@ -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<LoginResponse>(
|
||||
"/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<any>("/auth/sign-up", args);
|
||||
const res = await todolistApiInstance.post<any>("/auth/sign-up", args)
|
||||
|
||||
return res.data;
|
||||
return res.data
|
||||
},
|
||||
async logout() {
|
||||
const res = await todolistApiInstance.delete<LogoutResponse>("/auth/login");
|
||||
const res = await todolistApiInstance.delete<LogoutResponse>("/auth/login")
|
||||
|
||||
return handleError(res.data);
|
||||
return handleError(res.data)
|
||||
},
|
||||
|
||||
async me() {
|
||||
const res = await todolistApiInstance.get<MeResponse>("/auth/me");
|
||||
const res = await todolistApiInstance.get<MeResponse>("/auth/me")
|
||||
|
||||
return handleError(res.data);
|
||||
return handleError(res.data)
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./auth.api";
|
||||
export * from "./auth.hooks";
|
||||
export * from "./auth.api"
|
||||
export * from "./auth.hooks"
|
||||
|
||||
@@ -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<string> {
|
||||
const res = await todolistApiInstance.post<{
|
||||
refreshToken: string;
|
||||
accessToken: string;
|
||||
refreshToken: string
|
||||
accessToken: string
|
||||
}>(
|
||||
"/auth/refresh-token",
|
||||
{},
|
||||
@@ -22,76 +22,76 @@ async function refreshAccessToken(): Promise<string> {
|
||||
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)
|
||||
},
|
||||
);
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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<Todolist[]>("/todolists");
|
||||
const res = await todolistApiInstance.get<Todolist[]>("/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<DeleteTodolistResponse>(
|
||||
`/todolists/${todolistId}`
|
||||
);
|
||||
`/todolists/${todolistId}`,
|
||||
)
|
||||
|
||||
return res.data;
|
||||
return res.data
|
||||
},
|
||||
|
||||
async getTodolistTasks({ todolistId }: { todolistId: string }) {
|
||||
const res = await todolistApiInstance.get<TasksResponse>(
|
||||
`/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<CreateTaskResponse>(
|
||||
`/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<UpdateTaskResponse>(
|
||||
`/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<DeleteTaskResponse>(
|
||||
`/todolists/${todolistId}/tasks/${taskId}`
|
||||
);
|
||||
`/todolists/${todolistId}/tasks/${taskId}`,
|
||||
)
|
||||
|
||||
return res.data;
|
||||
return res.data
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
},
|
||||
});
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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<never>;
|
||||
export type MeResponse = ApiResponse<MeResponseData>;
|
||||
export type LoginResponse = LoginResponseData
|
||||
export type LogoutResponse = ApiResponse<never>
|
||||
export type MeResponse = ApiResponse<MeResponseData>
|
||||
|
||||
export type DeleteTodolistResponse = ApiResponse<never>;
|
||||
export type CreateTodolistResponse = ApiResponse<CreateTodolistResponseData>;
|
||||
export type DeleteTodolistResponse = ApiResponse<never>
|
||||
export type CreateTodolistResponse = ApiResponse<CreateTodolistResponseData>
|
||||
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",
|
||||
|
||||
Reference in New Issue
Block a user