From 107619004a6fe211f834cc5dcba265cbc2fd8620 Mon Sep 17 00:00:00 2001 From: Jungmin Date: Thu, 24 Oct 2024 23:52:10 +0900 Subject: [PATCH] =?UTF-8?q?reactor:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - todoStore로 전역에서 관리 - 토큰 유효 시간을 하루로 늘리고 HTTPS에서만 쿠키를 전송하고, 크로스사이트 공격을 방지하는 SameSite 옵션 추가 --- src/api/authAPI.ts | 17 --- src/api/goalAPI.ts | 9 -- src/api/noteAPI.ts | 3 - src/api/todoAPI.ts | 6 - src/app/(auth)/components/LoginForm.tsx | 6 +- src/app/(auth)/components/LoginPrompt.tsx | 5 +- src/app/(auth)/components/SignupPrompt.tsx | 5 +- src/app/Provider.tsx | 11 +- src/app/dashboard/components/NoteItem.tsx | 4 +- src/app/dashboard/components/NoteViewer.tsx | 4 +- src/app/dashboard/components/Sidebar.tsx | 62 +++++------ src/app/dashboard/components/TodoCard.tsx | 75 ++++--------- src/app/dashboard/components/TodoItem.tsx | 43 ++++---- src/app/dashboard/goal/[goalId]/page.tsx | 20 ++-- src/app/dashboard/note/[noteId]/page.tsx | 85 +-------------- src/app/dashboard/notes/[noteId]/page.tsx | 3 +- src/app/dashboard/page.tsx | 10 +- src/app/dashboard/todoboard/page.tsx | 6 +- src/app/layout.tsx | 9 +- src/components/CreateNewTodo.tsx | 40 +++---- src/components/EditGoalTitleModal.tsx | 4 + src/components/LinkUpload.tsx | 2 +- src/components/WhoRU.tsx | 25 ----- src/lib/api.ts | 2 +- src/store/todoStore.ts | 115 ++------------------ src/utils/AnimatedText.tsx | 2 +- src/utils/authUtils.ts | 3 +- 27 files changed, 146 insertions(+), 430 deletions(-) delete mode 100644 src/api/authAPI.ts delete mode 100644 src/components/WhoRU.tsx diff --git a/src/api/authAPI.ts b/src/api/authAPI.ts deleted file mode 100644 index b6384c2..0000000 --- a/src/api/authAPI.ts +++ /dev/null @@ -1,17 +0,0 @@ -import api from "@/lib/api"; - -export type SignupData = { - email: string; - nickname: string; - password: string; -}; - -export const postUser = async (data: SignupData) => { - const response = await api.post(`/user`, { - email: data.email, - name: data.nickname, - password: data.password, - }); - - return response.data; -}; diff --git a/src/api/goalAPI.ts b/src/api/goalAPI.ts index 14e334c..612cf0c 100644 --- a/src/api/goalAPI.ts +++ b/src/api/goalAPI.ts @@ -13,19 +13,13 @@ export interface ErrorType { }; } -// 목표 목록 가져오기 (GET) // 모든 목표를 가져오는 함수 export const getGoals = async () => { try { const response = await api.get(`/goals`); - // console.log(response); return response.data; } catch (error) { console.error("Goals fetch error:", error); - // const axiosError = error as AxiosError; - // toast.error( - // axiosError.response?.data?.message || axiosError.message || "목표 목록을 가져오는 중 오류가 발생했습니다.", - // ); } }; @@ -44,12 +38,9 @@ export const PostGoal = async (title: string) => { export const getGoal = async (id: number) => { try { const response = await api.get(`/goals/${id}`); - //console.log(response.data); return response.data; } catch (error) { console.error("Goal fetch error:", error); - // const axiosError = error as AxiosError; - // toast.error(axiosError.response?.data?.message || axiosError.message || "목표를 가져오는 중 오류가 발생했습니다."); } }; diff --git a/src/api/noteAPI.ts b/src/api/noteAPI.ts index 27bf8eb..37a126a 100644 --- a/src/api/noteAPI.ts +++ b/src/api/noteAPI.ts @@ -81,11 +81,8 @@ export async function patchNotes(noteId: number, title: string, content: string, export default async function deleteNote(noteId: number) { try { const response = await api.delete(`notes/${noteId}`); - console.log(response); - console.log(response.data); return response.data; } catch (e) { const error = e as ErrorType; - console.log(error); } } diff --git a/src/api/todoAPI.ts b/src/api/todoAPI.ts index fc455c2..385de8c 100644 --- a/src/api/todoAPI.ts +++ b/src/api/todoAPI.ts @@ -44,7 +44,6 @@ export const getTodos = async (id: number, done?: boolean, size?: number) => { const response = await api.get( `/todos?goalId=${id}&${done ? `done=${done}&` : done === false ? "done=false&" : ""}${size ? `size=${size}` : "size=27"}`, ); - //console.log(response.data); return response.data; } catch (error) { const axiosError = error as AxiosError; @@ -60,7 +59,6 @@ export const postFile = async (file: File) => { const response = await api.post(`/files`, formData, { headers: { "Content-Type": "multipart/form-data" }, }); - console.log(response.data); return response.data; } catch (error) { const axiosError = error as AxiosError; @@ -76,10 +74,7 @@ export const createTodo = async ( ): Promise => { try { const payload = { title, goalId, fileUrl, linkUrl }; - console.log(title, goalId); - console.log("😲"); const response = await api.post(`/todos`, payload); - console.log(response); return response.data; } catch (error) { console.log(error); @@ -92,7 +87,6 @@ export const createTodo = async ( export const updateTodo = async (todoId: number, updates: Partial): Promise => { try { const response = await api.patch(`/todos/${todoId}`, updates); - console.log(); return response.data; } catch (error) { const axiosError = error as AxiosError; diff --git a/src/app/(auth)/components/LoginForm.tsx b/src/app/(auth)/components/LoginForm.tsx index f3c4fef..d115dd0 100644 --- a/src/app/(auth)/components/LoginForm.tsx +++ b/src/app/(auth)/components/LoginForm.tsx @@ -38,7 +38,7 @@ export default function LoginForm() { setLoginError("로그인에 실패했습니다. 다시 시도해 주세요."); } } catch (error) { - const axiosError = error as AxiosError; + const axiosError = error as AxiosError; console.error("로그인 중 오류 발생:", axiosError); setLoginError("로그인 실패했습니다. 다시 시도해 주세요."); } @@ -56,12 +56,11 @@ export default function LoginForm() { aria-required="true" required className={`w-full rounded-tl-md rounded-tr-md border px-[10px] py-3 text-sm placeholder-slate-400 hover:bg-slate-50 focus:z-50 focus:outline-none focus:ring-1 focus:ring-blue-300 sm:px-4 sm:text-base ${ - errors.email ? "border-red-500 focus:ring-red-50" : "border-slate-300" + errors.email ? "border-red-50 focus:ring-red-50" : "border-slate-300" }`} {...register("email")} onBlur={() => trigger("email")} /> - {errors.email && {errors.email.message}} @@ -76,6 +75,7 @@ export default function LoginForm() { }`} onBlur={() => trigger("password")} /> + {errors.email && {errors.email.message}} {errors.password && {errors.password.message}} {loginError && {loginError}} diff --git a/src/components/EditGoalTitleModal.tsx b/src/components/EditGoalTitleModal.tsx index c1f7120..aa57d5f 100644 --- a/src/components/EditGoalTitleModal.tsx +++ b/src/components/EditGoalTitleModal.tsx @@ -6,6 +6,7 @@ import { AxiosError } from "axios"; import { useGoalStore, GoalType } from "@/store/goalStore"; import { ErrorType } from "@/api/goalAPI"; +import { useTodoStore } from "@/store/todoStore"; export type EditGoalTitleModalProps = { closeEditTitle: () => void; @@ -13,6 +14,7 @@ export type EditGoalTitleModalProps = { }; export default function EditGoalTitleModal({ closeEditTitle, goals }: EditGoalTitleModalProps) { + const { updateTodos } = useTodoStore(); const [title, setTitle] = useState(""); const { updateGoal } = useGoalStore(); @@ -21,7 +23,9 @@ export default function EditGoalTitleModal({ closeEditTitle, goals }: EditGoalTi try { await updateGoal(goals.id, title); + updateTodos(); closeEditTitle(); + toast.success("목표 제목이 수정되었습니다"); } catch (error) { const axiosError = error as AxiosError; toast.error(axiosError.message); diff --git a/src/components/LinkUpload.tsx b/src/components/LinkUpload.tsx index 7610e09..edbe7f5 100644 --- a/src/components/LinkUpload.tsx +++ b/src/components/LinkUpload.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; -import { InitialTodoType } from "@/app/Types/TodoGoalType"; +import { InitialTodoType } from "@/app/types/todoGoalType"; type LinkUploadProps = { closeSecond: () => void; diff --git a/src/components/WhoRU.tsx b/src/components/WhoRU.tsx deleted file mode 100644 index 144049a..0000000 --- a/src/components/WhoRU.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import { IoCloseOutline } from "react-icons/io5"; -import React, { ReactNode } from "react"; - -type ModalProps = { - children: ReactNode; - type: "first" | "second"; -}; - -export default function WhoRU({ children }: ModalProps) { - const handleClose = () => {}; - - return ( -
-
-
- {children} - -
-
- ); -} diff --git a/src/lib/api.ts b/src/lib/api.ts index 73161f6..8faac82 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -3,7 +3,7 @@ import axios from "axios"; import { useAuthStore } from "@/store/authStore"; const api = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_URL, // 여기에 실제 API 기본 URL을 설정하세요 + baseURL: process.env.NEXT_PUBLIC_API_URL, }); api.interceptors.request.use( diff --git a/src/store/todoStore.ts b/src/store/todoStore.ts index f6afafc..12cb7f7 100644 --- a/src/store/todoStore.ts +++ b/src/store/todoStore.ts @@ -1,114 +1,15 @@ import { create } from "zustand"; -import { createTodo, getAllTodos, getTodos, updateTodo } from "@/api/todoAPI"; - -export type TodoType = { - noteId?: number | null; - done: boolean; - linkUrl?: string | null; - fileUrl?: string | null; - title: string; - id: number; - goal: GoalType; - userId: number; - teamId: string; - updatedAt: string; - createdAt: string; -}; - -export type GoalType = { - id: number; - teamId: string; - title: string; - userId: number; - createdAt: string; - updatedAt: string; -}; - -// Todo 상태 정의 -export type TodoState = { - todos: TodoType[]; - fetchTodos: () => Promise; // 모든 할 일을 가져오는 메서드 - fetchTodosByGoal: (goalId: number) => Promise; // 특정 목표의 할 일을 가져오는 메서드 - setTodos: (todos: TodoType[]) => void; // setTodos 추가 - addTodo: (title: string, goalId: number, fileUrl?: string | null, linkUrl?: string | null) => Promise; - updateTodo: (todoId: number, updates: Partial) => Promise; - refreshTodos: () => Promise; - toggleTodo: (id: number) => void; // id를 받아서 toggle하도록 수정 +type TodoState = { + isUpdated: boolean; + updateTodos: () => void; }; +// Zustand 스토어 생성 export const useTodoStore = create((set) => ({ - todos: [], - fetchTodos: async () => { - try { - const response = await getAllTodos(); - const todos = response.todos; // todos 속성에서 배열을 가져옴 - console.log(todos); - if (Array.isArray(todos)) { - set({ todos }); - } else { - console.error("fetchTodos에서 반환된 값이 배열이 아닙니다.", todos); - } - } catch (error) { - console.error("모든 할 일 가져오는 중 오류 발생:", error); - } - }, - fetchTodosByGoal: async (goalId: number) => { - try { - const todos = await getTodos(goalId); // 목표에 해당하는 할 일 가져오기 - if (Array.isArray(todos)) { - set({ todos }); - } else { - console.error(`fetchTodosByGoal에서 반환된 값이 배열이 아닙니다.`, todos); - } - } catch (error) { - console.error(`목표 ID ${goalId}의 할 일 가져오는 중 오류 발생:`, error); - } - }, - setTodos: (todos: TodoType[]) => { - if (Array.isArray(todos)) { - set({ todos }); // todos가 배열이면 상태 업데이트 - } else { - console.error("setTodos에 전달된 값이 배열이 아닙니다.", todos); - } - }, - addTodo: async ( - title: string, - goalId: number, - fileUrl?: string | null, - linkUrl?: string | null, - ): Promise => { - const response = await createTodo(title, goalId, fileUrl, linkUrl); - const newTodo: TodoType = response; - - set((state) => ({ - todos: [...state.todos, newTodo], // 새 할 일을 배열의 끝에 추가 - })); - return newTodo; - }, - - updateTodo: async (todoId: number, updates: Partial): Promise => { - const response = await updateTodo(todoId, updates); // PatchGoal 호출 - const updatedTodo: TodoType = response; // 응답의 data에서 GoalType 추출 - - set((state) => ({ - todos: state.todos.map((todo) => (todo.id === todoId ? { ...todo, ...updatedTodo } : todo)), - })); - - return updatedTodo; - }, - - refreshTodos: async () => { - const todoListResponse = await getAllTodos(); - const todoList = todoListResponse?.todos || []; // 할 일 목록을 가져옴 - set({ todos: todoList }); // 상태 업데이트 - }, - - toggleTodo: (id: number) => { + isUpdated: false, // 초기값 + updateTodos: () => set((state) => ({ - todos: state.todos.map( - (t) => (t.id === id ? { ...t, done: !t.done } : t), // 현재 todo의 done 상태를 반전 - ), - })); - }, + isUpdated: !state.isUpdated, // 상태 업데이트 + })), })); diff --git a/src/utils/AnimatedText.tsx b/src/utils/AnimatedText.tsx index eed4bb7..73b28f8 100644 --- a/src/utils/AnimatedText.tsx +++ b/src/utils/AnimatedText.tsx @@ -37,7 +37,7 @@ const AnimatedText = ({ text, onClick }: AnimatedTextProps) => { return (