mirror of
https://github.com/ershisan99/todolist_next.git
synced 2025-12-17 05:09:30 +00:00
refactor
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
0
.idea/sonarlint/securityhotspotstore/index.pb
generated
Normal file
0
.idea/sonarlint/securityhotspotstore/index.pb
generated
Normal file
3989
package-lock.json
generated
3989
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -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
2874
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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")],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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}</>;
|
||||||
};
|
};
|
||||||
9
src/components/fullscreen-loader/index.tsx
Normal file
9
src/components/fullscreen-loader/index.tsx
Normal 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
6
src/components/index.ts
Normal 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";
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -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
3
src/constants/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./routes";
|
||||||
|
export * from "./query-keys";
|
||||||
|
export * from "./result-code";
|
||||||
5
src/constants/query-keys/index.ts
Normal file
5
src/constants/query-keys/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const QUERY_KEYS = {
|
||||||
|
TODOLISTS: "todolists",
|
||||||
|
TASKS: "tasks",
|
||||||
|
ME: "me",
|
||||||
|
} as const;
|
||||||
5
src/constants/result-code/index.ts
Normal file
5
src/constants/result-code/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export enum ResultCode {
|
||||||
|
Success = 0,
|
||||||
|
InvalidRequest = 1,
|
||||||
|
InvalidRequestWithCaptcha = 10,
|
||||||
|
}
|
||||||
4
src/constants/routes/index.ts
Normal file
4
src/constants/routes/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export const ROUTES = {
|
||||||
|
HOME: "/",
|
||||||
|
LOGIN: "/login",
|
||||||
|
} as const;
|
||||||
17
src/helpers/api-response/index.ts
Normal file
17
src/helpers/api-response/index.ts
Normal 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>;
|
||||||
11
src/helpers/handle-error/index.ts
Normal file
11
src/helpers/handle-error/index.ts
Normal 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
3
src/helpers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./handle-error";
|
||||||
|
export * from "./no-refetch";
|
||||||
|
export * from "./api-response";
|
||||||
8
src/helpers/no-refetch/index.ts
Normal file
8
src/helpers/no-refetch/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const noRefetch = {
|
||||||
|
refetchInterval: 1000 * 60 * 60,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
refetchOnMount: false,
|
||||||
|
refetchIntervalInBackground: false,
|
||||||
|
retry: false,
|
||||||
|
} as const;
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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("/");
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
28
src/services/auth/auth.api.ts
Normal file
28
src/services/auth/auth.api.ts
Normal 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);
|
||||||
|
},
|
||||||
|
};
|
||||||
40
src/services/auth/auth.hooks.ts
Normal file
40
src/services/auth/auth.hooks.ts
Normal 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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
6
src/services/auth/auth.instance.ts
Normal file
6
src/services/auth/auth.instance.ts
Normal 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,
|
||||||
|
});
|
||||||
2
src/services/auth/index.ts
Normal file
2
src/services/auth/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./auth.api";
|
||||||
|
export * from "./auth.hooks";
|
||||||
@@ -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]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -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 });
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import axios from "axios";
|
|
||||||
|
|
||||||
export const instance = axios.create({
|
|
||||||
baseURL: "https://social-network.samuraijs.com/api/1.1/",
|
|
||||||
withCredentials: true,
|
|
||||||
});
|
|
||||||
3
src/services/todolists/index.ts
Normal file
3
src/services/todolists/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./todolists.hooks";
|
||||||
|
export * from "./todolists.api";
|
||||||
|
export * from "./todolists.types";
|
||||||
84
src/services/todolists/todolists.api.ts
Normal file
84
src/services/todolists/todolists.api.ts
Normal 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;
|
||||||
|
},
|
||||||
|
};
|
||||||
82
src/services/todolists/todolists.hooks.ts
Normal file
82
src/services/todolists/todolists.hooks.ts
Normal 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]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
6
src/services/todolists/todolists.instance.ts
Normal file
6
src/services/todolists/todolists.instance.ts
Normal 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,
|
||||||
|
});
|
||||||
61
src/services/todolists/todolists.types.ts
Normal file
61
src/services/todolists/todolists.types.ts
Normal 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>;
|
||||||
@@ -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"]
|
||||||
|
|||||||
Reference in New Issue
Block a user