This commit is contained in:
2023-04-04 17:53:19 +02:00
parent 9a8c901130
commit f4f64702eb
37 changed files with 3344 additions and 4269 deletions

View File

@@ -4,7 +4,7 @@
"project": "./tsconfig.json" "project": "./tsconfig.json"
}, },
"plugins": ["@typescript-eslint"], "plugins": ["@typescript-eslint"],
"extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"], "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended", "@it-incubator/eslint-config"],
"rules": { "rules": {
"@typescript-eslint/consistent-type-imports": "warn" "@typescript-eslint/consistent-type-imports": "warn"
} }

View File

3989
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,27 +9,29 @@
"start": "next start" "start": "next start"
}, },
"dependencies": { "dependencies": {
"@tanstack/react-query": "^4.16.1", "@tanstack/react-query": "^4.28.0",
"axios": "^1.1.3", "axios": "^1.3.4",
"next": "13.0.2", "next": "13.2.4",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"zod": "^3.18.0" "zod": "^3.21.4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.0.0", "@it-incubator/eslint-config": "^0.1.0",
"@types/react": "^18.0.14", "@it-incubator/prettier-config": "^0.1.0",
"@types/react-dom": "^18.0.5", "@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.33.0", "@types/react": "^18.0.33",
"@typescript-eslint/parser": "^5.33.0", "@types/react-dom": "^18.0.11",
"autoprefixer": "^10.4.7", "@typescript-eslint/eslint-plugin": "^5.57.1",
"eslint": "^8.26.0", "@typescript-eslint/parser": "^5.57.1",
"eslint-config-next": "13.0.2", "autoprefixer": "^10.4.14",
"postcss": "^8.4.14", "eslint": "^8.37.0",
"prettier": "^2.7.1", "eslint-config-next": "13.2.4",
"prettier-plugin-tailwindcss": "^0.1.13", "postcss": "^8.4.21",
"tailwindcss": "^3.2.0", "prettier": "^2.8.7",
"typescript": "^4.8.4" "prettier-plugin-tailwindcss": "^0.2.6",
"tailwindcss": "^3.3.1",
"typescript": "^5.0.3"
}, },
"ct3aMetadata": { "ct3aMetadata": {
"initVersion": "6.10.1" "initVersion": "6.10.1"

2874
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
/** @type {import("prettier").Config} */ /** @type {import("prettier").Config} */
module.exports = { module.exports = {
plugins: [require.resolve("prettier-plugin-tailwindcss")], plugins: [require.resolve("prettier-plugin-tailwindcss"), require.resolve("@it-incubator/prettier-config")],
}; };

View File

@@ -1,8 +1,11 @@
import type { FC, ReactNode } from "react"; import type { FC, ReactNode } from "react";
import { useEffect } from "react"; import { useEffect } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useMeQuery } from "../services/hooks";
import { Loader } from "./loader"; import { Loader } from "../loader";
import { useMeQuery } from "@/services";
export const AuthRedirect: FC<{ children: ReactNode }> = ({ children }) => { export const AuthRedirect: FC<{ children: ReactNode }> = ({ children }) => {
const router = useRouter(); const router = useRouter();
@@ -22,5 +25,6 @@ export const AuthRedirect: FC<{ children: ReactNode }> = ({ children }) => {
</div> </div>
); );
} }
return <>{children}</>; return <>{children}</>;
}; };

View File

@@ -0,0 +1,9 @@
import { Loader } from "../loader";
export const FullscreenLoader = () => {
return (
<div className={"flex h-screen items-center justify-center"}>
<Loader />
</div>
);
};

6
src/components/index.ts Normal file
View File

@@ -0,0 +1,6 @@
export * from "./todolist";
export * from "./auth-redirect";
export * from "./button";
export * from "./input";
export * from "./loader";
export * from "./fullscreen-loader";

View File

@@ -1,7 +1,7 @@
export const Loader = () => { export const Loader = () => {
return ( return (
<div className={"flex h-full w-full items-center justify-center"}> <div className={"flex h-full w-full items-center justify-center"}>
<span className="loader"></span> <span className="loader" />
</div> </div>
); );
}; };

View File

@@ -1,44 +1,47 @@
import type { ChangeEvent, FC, MouseEventHandler } from "react"; 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 { import {
useCreateTaskMutation, useCreateTaskMutation,
useDeleteTaskMutation, useDeleteTaskMutation,
useDeleteTodolistMutation, useDeleteTodolistMutation,
useGetTasksQuery, useGetTasksQuery,
usePutTaskMutation, useUpdateTaskMutation,
} from "../services/hooks"; } from "@/services";
import { memo, useState } from "react";
import { Input } from "./Input";
import { Button } from "./Button";
type Props = { type Props = {
todolist: any; todolist: TodolistType;
}; };
type Filter = "all" | "active" | "completed"; type Filter = "all" | "active" | "completed";
const Todolist: FC<Props> = ({ todolist }) => { export const Todolist: FC<Props> = memo(({ todolist }) => {
const { data: tasks, isLoading } = useGetTasksQuery(todolist.id); const { data: tasks, isLoading } = useGetTasksQuery(todolist.id);
const { mutate: putTask } = usePutTaskMutation(); const { mutate: putTask } = useUpdateTaskMutation();
const { mutate: deleteTask } = useDeleteTaskMutation(); const { mutate: deleteTask } = useDeleteTaskMutation();
const { mutate: deleteTodolist } = useDeleteTodolistMutation(); const { mutate: deleteTodolist } = useDeleteTodolistMutation();
const { mutateAsync: createTask } = useCreateTaskMutation(); const { mutate: createTask } = useCreateTaskMutation();
const [newTaskTitle, setNewTaskTitle] = useState(""); const [newTaskTitle, setNewTaskTitle] = useState("");
const [filter, setFilter] = useState("all"); const [filter, setFilter] = useState("all");
console.log(newTaskTitle);
const handleChangeStatus = (todolistId: string, task: any) => { const handleChangeStatus = (todolistId: string, task: Task) => {
const newTask = { ...task, status: task.status === 0 ? 2 : 0 }; const newTask = { ...task, status: task.status === 0 ? 2 : 0 };
putTask({ todolistId, task: newTask }); putTask({ todolistId, task: newTask });
}; };
const handleDeleteTask = (todolistId: string, taskId: string) => { const handleDeleteTask = (todolistId: string, taskId: string) => {
deleteTask({ todolistId, taskId }); deleteTask({ todolistId, taskId });
}; };
const handleDeleteTodolist = (todolistId: string) => { const handleDeleteTodolist = (todolistId: string) => {
deleteTodolist(todolistId); deleteTodolist({ todolistId });
}; };
const handleAddTask = () => { const handleAddTask = () => {
createTask({ todolistId: todolist.id, title: newTaskTitle }).then((res) => { createTask({ todolistId: todolist.id, title: newTaskTitle });
if (res.data.resultCode === 0) setNewTaskTitle(""); setNewTaskTitle("");
});
}; };
const handleNewTaskTitleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleNewTaskTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
setNewTaskTitle(e.target.value); setNewTaskTitle(e.target.value);
@@ -49,7 +52,7 @@ const Todolist: FC<Props> = ({ todolist }) => {
}; };
if (isLoading) return <div>loading...</div>; if (isLoading) return <div>loading...</div>;
const filteredTasks = tasks?.data?.items?.filter((task: any) => { const filteredTasks = tasks?.items?.filter((task) => {
switch (filter) { switch (filter) {
case "active": case "active":
return task.status === 0; return task.status === 0;
@@ -59,6 +62,7 @@ const Todolist: FC<Props> = ({ todolist }) => {
return true; return true;
} }
}); });
return ( return (
<div <div
key={todolist.id} key={todolist.id}
@@ -74,7 +78,7 @@ const Todolist: FC<Props> = ({ todolist }) => {
<Input value={newTaskTitle} onChange={handleNewTaskTitleChange} /> <Input value={newTaskTitle} onChange={handleNewTaskTitleChange} />
<Button onClick={handleAddTask}>+</Button> <Button onClick={handleAddTask}>+</Button>
</div> </div>
{filteredTasks.map((task: any) => ( {filteredTasks?.map((task) => (
<div className={"flex items-center gap-2"} key={task.id}> <div className={"flex items-center gap-2"} key={task.id}>
<input <input
onChange={() => handleChangeStatus(todolist.id, task)} onChange={() => handleChangeStatus(todolist.id, task)}
@@ -100,6 +104,4 @@ const Todolist: FC<Props> = ({ todolist }) => {
</div> </div>
</div> </div>
); );
}; });
export default memo(Todolist);

3
src/constants/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from "./routes";
export * from "./query-keys";
export * from "./result-code";

View File

@@ -0,0 +1,5 @@
export const QUERY_KEYS = {
TODOLISTS: "todolists",
TASKS: "tasks",
ME: "me",
} as const;

View File

@@ -0,0 +1,5 @@
export enum ResultCode {
Success = 0,
InvalidRequest = 1,
InvalidRequestWithCaptcha = 10,
}

View File

@@ -0,0 +1,4 @@
export const ROUTES = {
HOME: "/",
LOGIN: "/login",
} as const;

View File

@@ -0,0 +1,17 @@
import type { ResultCode } from "@/constants";
type ApiResponseSuccess<T> = {
data: T;
messages?: never;
fieldsErrors?: never;
resultCode: ResultCode.Success;
};
type ApiResponseError<T> = {
data: T;
messages: string[];
fieldsErrors?: string[];
resultCode: ResultCode.InvalidRequest | ResultCode.InvalidRequestWithCaptcha;
};
export type ApiResponse<T> = ApiResponseSuccess<T> | ApiResponseError<T>;

View File

@@ -0,0 +1,11 @@
import type { ApiResponse } from "@/services";
export const handleError = <T>(data: ApiResponse<T>): ApiResponse<T> => {
if (data.resultCode !== 0) {
const error = data?.messages?.[0] || "An error has occurred";
throw new Error(error);
}
return data;
};

3
src/helpers/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from "./handle-error";
export * from "./no-refetch";
export * from "./api-response";

View File

@@ -0,0 +1,8 @@
export const noRefetch = {
refetchInterval: 1000 * 60 * 60,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
refetchIntervalInBackground: false,
retry: false,
} as const;

View File

@@ -1,9 +1,9 @@
import "../styles/globals.css";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { type AppType } from "next/dist/shared/lib/utils"; import { type AppType } from "next/dist/shared/lib/utils";
import "../styles/globals.css"; import { AuthRedirect } from "@/components";
import { QueryClient } from "@tanstack/query-core";
import { QueryClientProvider } from "@tanstack/react-query";
import { AuthRedirect } from "../components/AuthRedirect";
const queryClient = new QueryClient(); const queryClient = new QueryClient();

View File

@@ -1,16 +1,15 @@
import type { ChangeEvent } from "react";
import { useState } from "react";
import { type NextPage } from "next"; import { type NextPage } from "next";
import Head from "next/head"; import Head from "next/head";
import { Button } from "../components/Button";
import { Todolist, Button, FullscreenLoader, Input } from "@/components";
import { import {
useCreateTodolistMutation, useCreateTodolistMutation,
useLogoutMutation, useLogoutMutation,
useTodolistsQuery, useTodolistsQuery,
} from "../services/hooks"; } from "@/services";
import { Loader } from "../components/loader";
import { Input } from "../components/Input";
import type { ChangeEvent } from "react";
import { useState } from "react";
import Todolist from "../components/todolist";
const Home: NextPage = () => { const Home: NextPage = () => {
const [newTodolistTitle, setNewTodolistTitle] = useState(""); const [newTodolistTitle, setNewTodolistTitle] = useState("");
@@ -22,24 +21,19 @@ const Home: NextPage = () => {
logout(); logout();
}; };
const { mutateAsync: createTodolist } = useCreateTodolistMutation(); const { mutate: createTodolist } = useCreateTodolistMutation();
const handleAddTodolist = () => { const handleAddTodolist = () => {
createTodolist(newTodolistTitle).then((res) => { createTodolist({ title: newTodolistTitle });
if (res.data.resultCode === 0) setNewTodolistTitle(""); setNewTodolistTitle("");
});
}; };
const handleNewTodolistTitleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleNewTodolistTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
setNewTodolistTitle(e.target.value); setNewTodolistTitle(e.target.value);
}; };
if (isTodolistsLoading) if (isTodolistsLoading) return <FullscreenLoader />;
return (
<div className={"flex h-screen items-center justify-center"}>
<Loader />
</div>
);
return ( return (
<> <>
<Head> <Head>

View File

@@ -1,28 +1,27 @@
import React from "react"; import type { ChangeEvent } from "react";
import React, { useState } from "react";
import type { NextPage } from "next"; import type { NextPage } from "next";
import { Input } from "../components/Input";
import { Button } from "../components/Button"; import { Button, Input } from "@/components";
import { useLoginMutation } from "../services/hooks"; import { useLoginMutation } from "@/services";
import { useRouter } from "next/router";
import { useQueryClient } from "@tanstack/react-query";
const Login: NextPage = () => { const Login: NextPage = () => {
const { mutateAsync: login } = useLoginMutation(); const { mutate: login } = useLoginMutation();
const router = useRouter();
const queryClient = useQueryClient();
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
const [remember, setRemember] = React.useState(true);
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => { 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: React.ChangeEvent<HTMLInputElement>) => { const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value); setEmail(e.target.value);
}; };
const handleRememberChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleRememberChange = (e: ChangeEvent<HTMLInputElement>) => {
setRemember(e.target.checked); setRemember(e.target.checked);
}; };
@@ -31,9 +30,6 @@ const Login: NextPage = () => {
email, email,
password, password,
rememberMe: remember, rememberMe: remember,
}).then(() => {
queryClient.invalidateQueries(["me"]);
router.push("/");
}); });
}; };

View File

@@ -0,0 +1,28 @@
import { handleError } from "@/helpers";
import { authInstance } from "@/services/auth/auth.instance";
import type {
LoginResponse,
LogoutResponse,
MeResponse,
PostLoginArgs,
} from "@/services/todolists";
export const AuthApi = {
async login(args: PostLoginArgs) {
const res = await authInstance.post<LoginResponse>("/login", args);
return handleError(res.data);
},
async logout() {
const res = await authInstance.delete<LogoutResponse>("/login");
return handleError(res.data);
},
async me() {
const res = await authInstance.get<MeResponse>("/me");
return handleError(res.data);
},
};

View File

@@ -0,0 +1,40 @@
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/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();
return useMutation({
mutationFn: AuthApi.login,
onSuccess: async () => {
await queryClient.invalidateQueries([QUERY_KEYS.ME]);
await router.push(ROUTES.HOME);
},
});
};
export const useLogoutMutation = () => {
const queryClient = useQueryClient();
const router = useRouter();
return useMutation({
mutationFn: AuthApi.logout,
onSuccess: async () => {
await queryClient.invalidateQueries([QUERY_KEYS.ME]);
await router.push(ROUTES.LOGIN);
},
});
};

View File

@@ -0,0 +1,6 @@
import axios from "axios";
export const authInstance = axios.create({
baseURL: "https://social-network.samuraijs.com/api/1.1/auth/",
withCredentials: true,
});

View File

@@ -0,0 +1,2 @@
export * from "./auth.api";
export * from "./auth.hooks";

View File

@@ -1,126 +0,0 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { PostLoginArgs } from "./index";
import {
createTask,
createTodolist,
deleteMe,
deleteTask,
deleteTodolist,
getMe,
getTask,
getTodolists,
postLogin,
putTask,
} from "./index";
import { useRouter } from "next/router";
export const useLoginMutation = () => {
return useMutation({
mutationFn: (args: PostLoginArgs) => postLogin(args),
});
};
export const useMeQuery = () => {
return useQuery({
queryFn: () => getMe().then((res) => res.data),
queryKey: ["me"],
refetchInterval: 1000 * 60 * 60,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
refetchIntervalInBackground: false,
retry: false,
});
};
export const useLogoutMutation = () => {
const queryClient = useQueryClient();
const router = useRouter();
return useMutation({
mutationFn: () => deleteMe(),
onSuccess: () => {
queryClient.invalidateQueries(["me"]);
router.push("/login");
},
});
};
export const useTodolistsQuery = () => {
return useQuery({
queryFn: () => getTodolists().then((res) => res.data),
queryKey: ["todolists"],
refetchInterval: 1000 * 60 * 60,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
refetchIntervalInBackground: false,
retry: false,
});
};
export const useGetTasksQuery = (todolistId: string) => {
return useQuery({
queryKey: ["tasks", todolistId],
queryFn: () =>
getTask(todolistId).then((res) => {
return {
data: res.data,
todolistId,
};
}),
});
};
export const usePutTaskMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (args: any) => putTask(args.todolistId, args.task),
onSuccess: (res) => {
const todolistId = res.data.data.item.todoListId;
queryClient.invalidateQueries(["tasks", todolistId]);
},
});
};
export const useDeleteTaskMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (args: any) => deleteTask(args.todolistId, args.taskId),
onSuccess: (_, variables) => {
const todolistId = variables.todolistId;
queryClient.invalidateQueries(["tasks", todolistId]);
},
});
};
export const useCreateTodolistMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (title: string) => createTodolist(title),
onSuccess: () => {
queryClient.invalidateQueries(["todolists"]);
},
});
};
export const useDeleteTodolistMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (todolistId: string) => deleteTodolist(todolistId),
onSuccess: () => {
queryClient.invalidateQueries(["todolists"]);
},
});
};
export const useCreateTaskMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (args: { todolistId: string; title: string }) =>
createTask(args.todolistId, args.title),
onSuccess: (res) => {
const todolistId = res.data.data.item.todoListId;
queryClient.invalidateQueries(["tasks", todolistId]);
},
});
};

View File

@@ -1,65 +1,2 @@
import { instance } from "./instance"; export * from "./auth";
export * from "./todolists";
const handleError = (data: any) => {
if (data.resultCode === 0) {
return data;
} else {
throw new Error(data.messages[0]);
}
};
export interface Todolist {
id: string;
title: string;
addedDate: Date;
order: number;
}
export type PostLoginArgs = {
email: string;
password: string;
rememberMe: boolean;
};
export const postLogin = async (args: PostLoginArgs) => {
const data = await instance.post("auth/login", args);
return handleError(data.data);
};
export const getMe = async () => {
const data = await instance.get("auth/me");
return handleError(data.data);
};
export const deleteMe = async () => {
const data = await instance.delete("auth/login");
return handleError(data.data);
};
export const getTodolists = () => {
return instance.get<Todolist[]>("todo-lists");
};
export const getTask = (todolistId: string) => {
return instance.get(`todo-lists/${todolistId}/tasks`);
};
export const putTask = (todolistId: string, task: any) => {
const { id, ...rest } = task;
return instance.put(`todo-lists/${todolistId}/tasks/${id}`, rest);
};
export const deleteTask = (todolistId: string, taskId: string) => {
return instance.delete(`todo-lists/${todolistId}/tasks/${taskId}`);
};
export const createTodolist = (title: string) => {
return instance.post(`todo-lists`, { title });
};
export const deleteTodolist = (todolistId: string) => {
return instance.delete(`todo-lists/${todolistId}`);
};
export const createTask = (todolistId: string, title: string) => {
return instance.post(`todo-lists/${todolistId}/tasks`, { title });
};

View File

@@ -1,6 +0,0 @@
import axios from "axios";
export const instance = axios.create({
baseURL: "https://social-network.samuraijs.com/api/1.1/",
withCredentials: true,
});

View File

@@ -0,0 +1,3 @@
export * from "./todolists.hooks";
export * from "./todolists.api";
export * from "./todolists.types";

View File

@@ -0,0 +1,84 @@
import { todolistsInstance } from "./todolists.instance";
import { handleError } from "@/helpers";
import type {
CreateTaskResponse,
CreateTodolistResponse,
DeleteTaskResponse,
DeleteTodolistResponse,
Task,
TasksResponse,
Todolist,
UpdateTaskResponse,
} from "@/services";
export const TodolistAPI = {
async getTodolists() {
const res = await todolistsInstance.get<Todolist[]>("/");
return res.data;
},
async createTodolist({ title }: { title: string }) {
const res = await todolistsInstance.post<CreateTodolistResponse>("/", {
title,
});
return handleError(res.data);
},
async deleteTodolist({ todolistId }: { todolistId: string }) {
const res = await todolistsInstance.delete<DeleteTodolistResponse>(
`/${todolistId}`
);
return res.data;
},
async getTodolistTasks({ todolistId }: { todolistId: string }) {
const res = await todolistsInstance.get<TasksResponse>(
`/${todolistId}/tasks`
);
return res.data;
},
async createTask({
todolistId,
title,
}: {
todolistId: string;
title: string;
}) {
const res = await todolistsInstance.post<CreateTaskResponse>(
`/${todolistId}/tasks`,
{ title }
);
return handleError(res.data);
},
async updateTask({ todolistId, task }: { todolistId: string; task: Task }) {
const { id, ...rest } = task;
const res = await todolistsInstance.put<UpdateTaskResponse>(
`/${todolistId}/tasks/${id}`,
rest
);
return res.data;
},
async deleteTask({
todolistId,
taskId,
}: {
todolistId: string;
taskId: string;
}) {
const res = await todolistsInstance.delete<DeleteTaskResponse>(
`/${todolistId}/tasks/${taskId}`
);
return res.data;
},
};

View File

@@ -0,0 +1,82 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
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();
return useMutation({
mutationFn: TodolistAPI.createTodolist,
//todo: add onMutate
onSuccess: async () => {
await queryClient.invalidateQueries([QUERY_KEYS.TODOLISTS]);
},
});
};
export const useDeleteTodolistMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: TodolistAPI.deleteTodolist,
onSuccess: () => {
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();
return useMutation({
mutationFn: TodolistAPI.createTask,
onSuccess: (res) => {
const todolistId = res.data.item.todoListId;
queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]);
},
});
};
export const useUpdateTaskMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: TodolistAPI.updateTask,
onSuccess: async (res) => {
const todolistId = res.data.item.todoListId;
await queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]);
},
});
};
export const useDeleteTaskMutation = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: TodolistAPI.deleteTask,
onSuccess: (_, variables) => {
const todolistId = variables.todolistId;
queryClient.invalidateQueries([QUERY_KEYS.TASKS, todolistId]);
},
});
};

View File

@@ -0,0 +1,6 @@
import axios from "axios";
export const todolistsInstance = axios.create({
baseURL: "https://social-network.samuraijs.com/api/1.1/todo-lists",
withCredentials: true,
});

View File

@@ -0,0 +1,61 @@
import type { ApiResponse } from "@/helpers";
export type UpdateTaskResponseData = {
item: Task;
};
export type TasksResponse = {
items: Task[];
totalCount: number;
error?: string;
};
export type Task = {
id: string;
title: string;
description?: string;
todoListId: string;
order: number;
status: number;
priority: number;
startDate?: Date;
deadline?: Date;
addedDate: Date;
};
export type Todolist = {
id: string;
title: string;
addedDate: Date;
order: number;
};
export type PostLoginArgs = {
email: string;
password: string;
rememberMe: boolean;
};
export type LoginResponseData = {
userId: number;
};
export type MeResponseData = {
id: number;
login: string;
email: string;
};
export type LoginResponse = ApiResponse<LoginResponseData>;
export type LogoutResponse = ApiResponse<never>;
export type MeResponse = ApiResponse<MeResponseData>;
export type DeleteTodolistResponse = ApiResponse<never>;
export type CreateTodolistResponse = ApiResponse<CreateTodolistResponseData>;
export type CreateTodolistResponseData = {
item: Todolist;
};
export type CreateTaskResponse = ApiResponse<{ item: Task }>;
export type DeleteTaskResponse = ApiResponse<never>;
export type UpdateTaskResponse = ApiResponse<UpdateTaskResponseData>;

View File

@@ -14,7 +14,11 @@
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"incremental": true, "incremental": true,
"noUncheckedIndexedAccess": true "noUncheckedIndexedAccess": true,
"paths": {
"@/*": ["./src/*"]
}
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"],
"exclude": ["node_modules"] "exclude": ["node_modules"]