From 380036b8b72817ff4fd9121ba491aef29ce880fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sat, 7 Dec 2024 17:00:00 +0900
Subject: [PATCH 01/10] =?UTF-8?q?chore:=20merge=20request=20template=20?=
 =?UTF-8?q?=EC=A0=81=EC=9A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../merge_requesttemplates.md}                                    | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename .gitlab/{issue_templates/merge_request_templates.md => merge_request_templates/merge_requesttemplates.md} (100%)

diff --git a/.gitlab/issue_templates/merge_request_templates.md b/.gitlab/merge_request_templates/merge_requesttemplates.md
similarity index 100%
rename from .gitlab/issue_templates/merge_request_templates.md
rename to .gitlab/merge_request_templates/merge_requesttemplates.md
-- 
GitLab


From 9df180baae59b8b3c6dc160b7d967e6223e88c1a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sat, 7 Dec 2024 18:29:01 +0900
Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20api=20?=
 =?UTF-8?q?=EC=9E=91=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/api/friend.js | 126 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 126 insertions(+)
 create mode 100644 src/api/friend.js

diff --git a/src/api/friend.js b/src/api/friend.js
new file mode 100644
index 0000000..7a5a7c7
--- /dev/null
+++ b/src/api/friend.js
@@ -0,0 +1,126 @@
+// 기본 API URL
+const BASE_URL = process.env.REACT_APP_BASE_URL;
+
+/**
+ * 친구 요청 보내기
+ * @param {Object} requestData - 요청 데이터 (userId, email)
+ * @returns {Promise<Object>} - 생성된 친구 요청 데이터
+ */
+export const sendFriendRequest = async (requestData) => {
+  const response = await fetch(`${BASE_URL}/api/friend/request`, {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json",
+    },
+    body: JSON.stringify(requestData),
+  });
+
+  if (!response.ok) {
+    throw new Error("Failed to send friend request");
+  }
+
+  return await response.json();
+};
+
+/**
+ * 받은 친구 요청 조회
+ * @returns {Promise<Object[]>} - 받은 친구 요청 리스트
+ */
+export const getReceivedFriendRequests = async () => {
+  const response = await fetch(`${BASE_URL}/api/friend/requests/received`);
+
+  if (!response.ok) {
+    throw new Error("Failed to fetch received friend requests");
+  }
+
+  return (await response.json()).data;
+};
+
+/**
+ * 보낸 친구 요청 조회
+ * @returns {Promise<Object[]>} - 보낸 친구 요청 리스트
+ */
+export const getSentFriendRequests = async () => {
+  const response = await fetch(`${BASE_URL}/api/friend/requests/sent`);
+
+  if (!response.ok) {
+    throw new Error("Failed to fetch sent friend requests");
+  }
+
+  return (await response.json()).data;
+};
+
+/**
+ * 친구 요청 수락
+ * @param {number} requestId - 친구 요청 ID
+ * @returns {Promise<Object>} - 수락된 친구 요청 데이터
+ */
+export const acceptFriendRequest = async (requestId) => {
+  const response = await fetch(
+    `${BASE_URL}/api/friend/request/${requestId}/accept`,
+    {
+      method: "POST",
+    }
+  );
+
+  if (!response.ok) {
+    throw new Error("Failed to accept friend request");
+  }
+
+  return (await response.json()).data;
+};
+
+/**
+ * 친구 요청 거절
+ * @param {number} requestId - 친구 요청 ID
+ * @returns {Promise<Object>} - 거절된 친구 요청 데이터
+ */
+export const rejectFriendRequest = async (requestId) => {
+  const response = await fetch(
+    `${BASE_URL}/api/friend/request/${requestId}/reject`,
+    {
+      method: "POST",
+    }
+  );
+
+  if (!response.ok) {
+    throw new Error("Failed to reject friend request");
+  }
+
+  return (await response.json()).data;
+};
+
+/**
+ * 친구 목록 조회
+ * @param {number} page - 페이지 번호
+ * @param {number} size - 페이지 크기
+ * @returns {Promise<Object>} - 친구 목록 데이터
+ */
+export const getAllFriends = async (page = 0, size = 10) => {
+  const response = await fetch(
+    `${BASE_URL}/api/friend/all?page=${page}&size=${size}`
+  );
+
+  if (!response.ok) {
+    throw new Error("Failed to fetch friends list");
+  }
+
+  return (await response.json()).data.content;
+};
+
+/**
+ * 친구 삭제
+ * @param {number} friendId - 삭제할 친구 ID
+ * @returns {Promise<Object>} - 삭제 결과 데이터
+ */
+export const deleteFriend = async (friendId) => {
+  const response = await fetch(`${BASE_URL}/api/friends/${friendId}`, {
+    method: "DELETE",
+  });
+
+  if (!response.ok) {
+    throw new Error("Failed to delete friend");
+  }
+
+  return await response.json();
+};
-- 
GitLab


From c84064e425539fe08baa3cecddee6cb74c90dd51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sun, 8 Dec 2024 02:13:30 +0900
Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?=
 =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=8D=BC=EB=B8=94=EB=A6=AC=EC=8B=B1=20(#1?=
 =?UTF-8?q?1)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/Card.jsx             |  51 +++---
 src/components/layout/HeaderNav.jsx |   2 +-
 src/pages/Mypage.jsx                | 254 +++++++++++++++++++++++++++-
 src/store/authStore.js              |   4 +-
 4 files changed, 283 insertions(+), 28 deletions(-)

diff --git a/src/components/Card.jsx b/src/components/Card.jsx
index 5dfcc0e..7f936f0 100644
--- a/src/components/Card.jsx
+++ b/src/components/Card.jsx
@@ -2,7 +2,8 @@ import { cn } from "../libs/index";
 import { cva } from "class-variance-authority";
 import React from "react";
 
-const cardVariants = cva("w-[20rem] rounded-xl shadow-lg p-4 overflow-hidden", {
+// Card 스타일 변수를 정의합니다.
+const cardVariants = cva("w-full rounded-xl shadow-lg p-4 overflow-hidden", {
   variants: {
     theme: {
       black: "bg-black text-white",
@@ -11,35 +12,39 @@ const cardVariants = cva("w-[20rem] rounded-xl shadow-lg p-4 overflow-hidden", {
       purple: "bg-gradient-purple text-white",
       indigo: "bg-gradient-indigo text-white",
       mix: "bg-gradient-mix text-white",
+      gray: "bg-gray-300 text-gray-500",
     },
   },
 });
 
-export default function Card({
-  image,
-  title,
-  content,
-  time,
-  headCount,
-  theme = "black",
-  onClick,
-}) {
-  const variantClass = cardVariants({ theme });
+export default function Card({ meeting, theme = "black", onClick }) {
+  const {
+    title,
+    timeIdxStart,
+    timeIdxEnd,
+    location,
+    creatorName,
+    time_idx_deadline,
+    type,
+  } = meeting;
+
+  const variantClass = cardVariants({
+    theme: type === "CLOSE" ? "gray" : theme,
+  });
 
   return (
     <div className={cn(variantClass)} onClick={onClick}>
-      {image && (
-        <img
-          src={image}
-          alt={title}
-          className="object-contain w-full h-48 rounded-xl"
-        />
-      )}
-      <div className="p-3">
-        <h3 className="mb-2 text-xl font-bold">{title}</h3>
-        <p className="text-base">{content}</p>
-        <p className="text-base">{time}</p>
-        <p className="mt-5 text-right">{headCount}</p>
+      <h3 className="mb-2 text-xl font-bold">{title}</h3>
+      <p className="text-sm text-gray-700">{location}</p>
+      <p className="text-base">
+        시간: {timeIdxStart} ~ {timeIdxEnd}
+      </p>
+      <p className="text-base">마감 시간: {time_idx_deadline}</p>
+      <div className="flex justify-between mt-2">
+        <span className="text-sm text-gray-600">작성자: {creatorName}</span>
+        <span className="text-sm text-right">
+          {type === "OPEN" ? "참여 가능" : "참여 마감"}
+        </span>
       </div>
     </div>
   );
diff --git a/src/components/layout/HeaderNav.jsx b/src/components/layout/HeaderNav.jsx
index 2dba5ad..7a1b350 100644
--- a/src/components/layout/HeaderNav.jsx
+++ b/src/components/layout/HeaderNav.jsx
@@ -77,7 +77,7 @@ export default function HeaderNav() {
                 icon={<LogoIcon fillColor="#ffffff" />}
                 onClick={navigateToChattingList}
               >
-                번개채팅방
+                번개모임
               </Button>
               {user ? (
                 <Button
diff --git a/src/pages/Mypage.jsx b/src/pages/Mypage.jsx
index 7ec5065..d9eef68 100644
--- a/src/pages/Mypage.jsx
+++ b/src/pages/Mypage.jsx
@@ -1,7 +1,257 @@
-import React from "react";
+import React, { useState, useEffect } from "react";
+import useAuthStore from "../store/authStore";
+import {
+  getReceivedFriendRequests,
+  sendFriendRequest,
+  getAllFriends,
+  acceptFriendRequest,
+  rejectFriendRequest,
+  deleteFriend,
+} from "../api/friend";
+import Button from "../components/Button";
+import LogoIcon from "../components/icons/LogoIcon";
 
 const MyPage = () => {
-  return <></>;
+  const { user } = useAuthStore(); // Zustand에서 user 상태 가져오기
+  const [activeTab, setActiveTab] = useState("lightning"); // 현재 활성화된 탭
+  const [receivedRequests, setReceivedRequests] = useState([]);
+  const [sentRequests, setSentRequests] = useState([]);
+  const [friends, setFriends] = useState([]);
+  const [email, setEmail] = useState(""); // 친구 요청할 이메일
+  const [page, setPage] = useState(0); // 친구 목록 페이지
+  const [hasNext, setHasNext] = useState(true); // 페이지네이션 상태
+  const [isLoading, setIsLoading] = useState(false);
+
+  // 탭 변경 함수
+  const switchTab = (tab) => setActiveTab(tab);
+
+  // 받은 친구 요청 조회
+  useEffect(() => {
+    const fetchReceivedRequests = async () => {
+      try {
+        const data = await getReceivedFriendRequests();
+        setReceivedRequests(data);
+      } catch (error) {
+        console.error("Failed to fetch received requests:", error);
+      }
+    };
+    if (activeTab === "friends") fetchReceivedRequests();
+  }, [activeTab]);
+
+  // 친구 목록 무한스크롤 처리
+  useEffect(() => {
+    const fetchFriends = async () => {
+      if (!hasNext || isLoading) return;
+      try {
+        setIsLoading(true);
+        const data = await getAllFriends(page, 10);
+        setFriends((prev) => [...prev, ...data.content]);
+        setHasNext(data.hasNext);
+        setPage((prev) => prev + 1);
+      } catch (error) {
+        console.error("Failed to fetch friends:", error);
+      } finally {
+        setIsLoading(false);
+      }
+    };
+    if (activeTab === "friends") fetchFriends();
+  }, [page, hasNext, activeTab]);
+
+  // 친구 요청 보내기
+  const handleSendRequest = async () => {
+    try {
+      const requestData = { email };
+      const response = await sendFriendRequest(requestData);
+      setSentRequests((prev) => [...prev, response.data]);
+      setEmail(""); // 입력 필드 초기화
+    } catch (error) {
+      console.error("Failed to send friend request:", error);
+    }
+  };
+
+  // 친구 요청 수락
+  const handleAcceptRequest = async (requestId) => {
+    try {
+      const response = await acceptFriendRequest(requestId);
+      setReceivedRequests((prev) =>
+        prev.filter((request) => request.id !== requestId)
+      );
+      setFriends((prev) => [response, ...prev]); // 친구 목록에 추가
+    } catch (error) {
+      console.error("Failed to accept request:", error);
+    }
+  };
+
+  // 친구 요청 거절
+  const handleRejectRequest = async (requestId) => {
+    try {
+      await rejectFriendRequest(requestId);
+      setReceivedRequests((prev) =>
+        prev.filter((request) => request.id !== requestId)
+      );
+    } catch (error) {
+      console.error("Failed to reject request:", error);
+    }
+  };
+
+  // 친구 삭제
+  const handleDeleteFriend = async (friendId) => {
+    try {
+      await deleteFriend(friendId);
+      setFriends((prev) => prev.filter((friend) => friend.id !== friendId));
+    } catch (error) {
+      console.error("Failed to delete friend:", error);
+    }
+  };
+
+  return (
+    <div className="w-full max-w-screen-lg min-h-screen mx-auto bg-white">
+      {/* 프로필 영역 */}
+      <div className="flex items-center px-6 py-4 border-b">
+        <div className="flex-shrink-0">
+          {/* <img
+            src={user?.profilePicture || "https://via.placeholder.com/150"}
+            alt="프로필 사진"
+            className="object-cover w-20 h-20 rounded-full"
+          /> */}
+          <LogoIcon className="w-20 h-20 rounded-full" />
+        </div>
+        <div className="flex-grow ml-6">
+          <h1 className="text-xl font-bold">{user?.name || "Guest"}</h1>
+          <p className="text-gray-600">{user?.email || "guest@example.com"}</p>
+        </div>
+      </div>
+
+      {/* 탭 네비게이션 */}
+      <div className="flex justify-center py-2 space-x-6 border-b">
+        <button
+          className={`px-4 py-2 text-sm font-medium ${
+            activeTab === "lightning"
+              ? "text-secondary-500 border-b-2 border-secondary-500"
+              : "text-gray-600"
+          }`}
+          onClick={() => switchTab("lightning")}
+        >
+          번개 채팅방
+        </button>
+        <button
+          className={`px-4 py-2 text-sm font-medium ${
+            activeTab === "friends"
+              ? "text-tertiary-500 border-b-2 border-tertiary-500"
+              : "text-gray-600"
+          }`}
+          onClick={() => switchTab("friends")}
+        >
+          친구
+        </button>
+      </div>
+
+      {/* 친구 탭 */}
+      {activeTab === "friends" && (
+        <div className="p-4 space-y-8">
+          {/* 받은 친구 요청 */}
+          <div>
+            <h2 className="text-lg font-bold">받은 친구 요청</h2>
+            <div className="space-y-4">
+              {receivedRequests.map((request) => (
+                <div
+                  key={request.id}
+                  className="flex items-center justify-between p-4 border rounded-lg"
+                >
+                  <div>
+                    <h3 className="font-semibold text-md">
+                      {request.requester.name}
+                    </h3>
+                    <p className="text-sm text-gray-600">
+                      {request.requester.email}
+                    </p>
+                  </div>
+                  <div className="space-x-2">
+                    <Button
+                      size="md"
+                      theme="indigo"
+                      onClick={() => handleAcceptRequest(request.id)}
+                    >
+                      수락
+                    </Button>
+                    <Button
+                      size="md"
+                      theme="pink"
+                      onClick={() => handleRejectRequest(request.id)}
+                    >
+                      거절
+                    </Button>
+                  </div>
+                </div>
+              ))}
+            </div>
+          </div>
+          <hr />
+          {/* 친구 요청하기 */}
+          <div>
+            <h2 className="text-lg font-bold">친구 요청하기</h2>
+            <div className="flex items-center my-4 space-x-4">
+              <input
+                type="email"
+                className="flex-1 min-w-0 p-3 border rounded-full"
+                placeholder="친구 이메일 입력"
+                value={email}
+                onChange={(e) => setEmail(e.target.value)}
+              />
+              <Button
+                size="md"
+                theme="indigo"
+                className="min-w-[50px]"
+                onClick={handleSendRequest}
+              >
+                요청
+              </Button>
+            </div>
+            <div className="mt-4 space-y-4">
+              {sentRequests.map((request) => (
+                <div key={request.id} className="p-2 border rounded-lg">
+                  <p className="text-sm">
+                    {request.receiver.name} ({request.receiver.email})
+                  </p>
+                  <p className="text-xs text-gray-500">{request.status}</p>
+                </div>
+              ))}
+            </div>
+          </div>
+          <hr />
+          {/* 친구 목록 */}
+          <div>
+            <h2 className="text-lg font-bold">친구 목록</h2>
+            <div className="space-y-4">
+              {friends.map((friend) => (
+                <div
+                  key={friend.id}
+                  className="flex items-center justify-between p-4 border rounded-lg"
+                >
+                  <div>
+                    <h3 className="font-semibold text-md">
+                      {friend.friendInfo.name}
+                    </h3>
+                    <p className="text-sm text-gray-600">
+                      {friend.friendInfo.email}
+                    </p>
+                  </div>
+                  <button
+                    className="px-4 py-2 text-sm text-white bg-red-500 rounded-lg hover:bg-red-600"
+                    onClick={() => handleDeleteFriend(friend.id)}
+                  >
+                    삭제
+                  </button>
+                </div>
+              ))}
+              {isLoading && <p>로딩 중...</p>}
+              {!hasNext && <p>더 이상 친구가 없습니다.</p>}
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  );
 };
 
 export default MyPage;
diff --git a/src/store/authStore.js b/src/store/authStore.js
index 1d1223b..566047c 100644
--- a/src/store/authStore.js
+++ b/src/store/authStore.js
@@ -2,8 +2,8 @@ import { create } from "zustand";
 import { getSessionInfo, logout } from "../api/auth";
 
 const useAuthStore = create((set) => ({
-  user: null, // 사용자 정보
-  // user: { name: "윤석찬", email: "ysc0731@ajou.ac.kr" }, // 사용자 정보
+  // user: null, // 사용자 정보
+  user: { name: "윤석찬", email: "ysc0731@ajou.ac.kr" }, // 사용자 정보
 
   // 세션 정보 가져오기
   fetchSession: async () => {
-- 
GitLab


From f76ac68951043a368719ac8fcd5d625b52f8fe2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sat, 7 Dec 2024 17:00:00 +0900
Subject: [PATCH 04/10] =?UTF-8?q?chore:=20merge=20request=20template=20?=
 =?UTF-8?q?=EC=A0=81=EC=9A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/store/authStore.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/store/authStore.js b/src/store/authStore.js
index 05da3b6..4e6141e 100644
--- a/src/store/authStore.js
+++ b/src/store/authStore.js
@@ -10,7 +10,7 @@ const useAuthStore = create((set) => ({
     set({ loading: true });
     try {
       const userInfo = await getSessionInfo();
-      set({ user: userInfo });
+      set(userInfo);
     } catch (error) {
       console.error("Failed to fetch session info:", error);
       set({ user: null });
-- 
GitLab


From 64b2595d02220f7b4731e1aa8557bfeae6604107 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sat, 7 Dec 2024 18:29:01 +0900
Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=EC=B9=9C=EA=B5=AC=20api=20?=
 =?UTF-8?q?=EC=9E=91=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/api/friend.js | 126 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 126 insertions(+)
 create mode 100644 src/api/friend.js

diff --git a/src/api/friend.js b/src/api/friend.js
new file mode 100644
index 0000000..7a5a7c7
--- /dev/null
+++ b/src/api/friend.js
@@ -0,0 +1,126 @@
+// 기본 API URL
+const BASE_URL = process.env.REACT_APP_BASE_URL;
+
+/**
+ * 친구 요청 보내기
+ * @param {Object} requestData - 요청 데이터 (userId, email)
+ * @returns {Promise<Object>} - 생성된 친구 요청 데이터
+ */
+export const sendFriendRequest = async (requestData) => {
+  const response = await fetch(`${BASE_URL}/api/friend/request`, {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json",
+    },
+    body: JSON.stringify(requestData),
+  });
+
+  if (!response.ok) {
+    throw new Error("Failed to send friend request");
+  }
+
+  return await response.json();
+};
+
+/**
+ * 받은 친구 요청 조회
+ * @returns {Promise<Object[]>} - 받은 친구 요청 리스트
+ */
+export const getReceivedFriendRequests = async () => {
+  const response = await fetch(`${BASE_URL}/api/friend/requests/received`);
+
+  if (!response.ok) {
+    throw new Error("Failed to fetch received friend requests");
+  }
+
+  return (await response.json()).data;
+};
+
+/**
+ * 보낸 친구 요청 조회
+ * @returns {Promise<Object[]>} - 보낸 친구 요청 리스트
+ */
+export const getSentFriendRequests = async () => {
+  const response = await fetch(`${BASE_URL}/api/friend/requests/sent`);
+
+  if (!response.ok) {
+    throw new Error("Failed to fetch sent friend requests");
+  }
+
+  return (await response.json()).data;
+};
+
+/**
+ * 친구 요청 수락
+ * @param {number} requestId - 친구 요청 ID
+ * @returns {Promise<Object>} - 수락된 친구 요청 데이터
+ */
+export const acceptFriendRequest = async (requestId) => {
+  const response = await fetch(
+    `${BASE_URL}/api/friend/request/${requestId}/accept`,
+    {
+      method: "POST",
+    }
+  );
+
+  if (!response.ok) {
+    throw new Error("Failed to accept friend request");
+  }
+
+  return (await response.json()).data;
+};
+
+/**
+ * 친구 요청 거절
+ * @param {number} requestId - 친구 요청 ID
+ * @returns {Promise<Object>} - 거절된 친구 요청 데이터
+ */
+export const rejectFriendRequest = async (requestId) => {
+  const response = await fetch(
+    `${BASE_URL}/api/friend/request/${requestId}/reject`,
+    {
+      method: "POST",
+    }
+  );
+
+  if (!response.ok) {
+    throw new Error("Failed to reject friend request");
+  }
+
+  return (await response.json()).data;
+};
+
+/**
+ * 친구 목록 조회
+ * @param {number} page - 페이지 번호
+ * @param {number} size - 페이지 크기
+ * @returns {Promise<Object>} - 친구 목록 데이터
+ */
+export const getAllFriends = async (page = 0, size = 10) => {
+  const response = await fetch(
+    `${BASE_URL}/api/friend/all?page=${page}&size=${size}`
+  );
+
+  if (!response.ok) {
+    throw new Error("Failed to fetch friends list");
+  }
+
+  return (await response.json()).data.content;
+};
+
+/**
+ * 친구 삭제
+ * @param {number} friendId - 삭제할 친구 ID
+ * @returns {Promise<Object>} - 삭제 결과 데이터
+ */
+export const deleteFriend = async (friendId) => {
+  const response = await fetch(`${BASE_URL}/api/friends/${friendId}`, {
+    method: "DELETE",
+  });
+
+  if (!response.ok) {
+    throw new Error("Failed to delete friend");
+  }
+
+  return await response.json();
+};
-- 
GitLab


From 632b2856208ac5cba035c3cc6605c476f9b98b75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sun, 8 Dec 2024 02:13:30 +0900
Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?=
 =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=8D=BC=EB=B8=94=EB=A6=AC=EC=8B=B1=20(#1?=
 =?UTF-8?q?1)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/Card.jsx             |  51 +++---
 src/components/layout/HeaderNav.jsx |   2 +-
 src/pages/Mypage.jsx                | 254 +++++++++++++++++++++++++++-
 3 files changed, 281 insertions(+), 26 deletions(-)

diff --git a/src/components/Card.jsx b/src/components/Card.jsx
index 5dfcc0e..7f936f0 100644
--- a/src/components/Card.jsx
+++ b/src/components/Card.jsx
@@ -2,7 +2,8 @@ import { cn } from "../libs/index";
 import { cva } from "class-variance-authority";
 import React from "react";
 
-const cardVariants = cva("w-[20rem] rounded-xl shadow-lg p-4 overflow-hidden", {
+// Card 스타일 변수를 정의합니다.
+const cardVariants = cva("w-full rounded-xl shadow-lg p-4 overflow-hidden", {
   variants: {
     theme: {
       black: "bg-black text-white",
@@ -11,35 +12,39 @@ const cardVariants = cva("w-[20rem] rounded-xl shadow-lg p-4 overflow-hidden", {
       purple: "bg-gradient-purple text-white",
       indigo: "bg-gradient-indigo text-white",
       mix: "bg-gradient-mix text-white",
+      gray: "bg-gray-300 text-gray-500",
     },
   },
 });
 
-export default function Card({
-  image,
-  title,
-  content,
-  time,
-  headCount,
-  theme = "black",
-  onClick,
-}) {
-  const variantClass = cardVariants({ theme });
+export default function Card({ meeting, theme = "black", onClick }) {
+  const {
+    title,
+    timeIdxStart,
+    timeIdxEnd,
+    location,
+    creatorName,
+    time_idx_deadline,
+    type,
+  } = meeting;
+
+  const variantClass = cardVariants({
+    theme: type === "CLOSE" ? "gray" : theme,
+  });
 
   return (
     <div className={cn(variantClass)} onClick={onClick}>
-      {image && (
-        <img
-          src={image}
-          alt={title}
-          className="object-contain w-full h-48 rounded-xl"
-        />
-      )}
-      <div className="p-3">
-        <h3 className="mb-2 text-xl font-bold">{title}</h3>
-        <p className="text-base">{content}</p>
-        <p className="text-base">{time}</p>
-        <p className="mt-5 text-right">{headCount}</p>
+      <h3 className="mb-2 text-xl font-bold">{title}</h3>
+      <p className="text-sm text-gray-700">{location}</p>
+      <p className="text-base">
+        시간: {timeIdxStart} ~ {timeIdxEnd}
+      </p>
+      <p className="text-base">마감 시간: {time_idx_deadline}</p>
+      <div className="flex justify-between mt-2">
+        <span className="text-sm text-gray-600">작성자: {creatorName}</span>
+        <span className="text-sm text-right">
+          {type === "OPEN" ? "참여 가능" : "참여 마감"}
+        </span>
       </div>
     </div>
   );
diff --git a/src/components/layout/HeaderNav.jsx b/src/components/layout/HeaderNav.jsx
index f274349..426f712 100644
--- a/src/components/layout/HeaderNav.jsx
+++ b/src/components/layout/HeaderNav.jsx
@@ -89,7 +89,7 @@ export default function HeaderNav() {
                 icon={<ChatIcon />}
                 onClick={navigateToChattingList}
               >
-                번개채팅방
+                번개모임
               </Button>
               {user ? (
                 <Button
diff --git a/src/pages/Mypage.jsx b/src/pages/Mypage.jsx
index 7ec5065..d9eef68 100644
--- a/src/pages/Mypage.jsx
+++ b/src/pages/Mypage.jsx
@@ -1,7 +1,257 @@
-import React from "react";
+import React, { useState, useEffect } from "react";
+import useAuthStore from "../store/authStore";
+import {
+  getReceivedFriendRequests,
+  sendFriendRequest,
+  getAllFriends,
+  acceptFriendRequest,
+  rejectFriendRequest,
+  deleteFriend,
+} from "../api/friend";
+import Button from "../components/Button";
+import LogoIcon from "../components/icons/LogoIcon";
 
 const MyPage = () => {
-  return <></>;
+  const { user } = useAuthStore(); // Zustand에서 user 상태 가져오기
+  const [activeTab, setActiveTab] = useState("lightning"); // 현재 활성화된 탭
+  const [receivedRequests, setReceivedRequests] = useState([]);
+  const [sentRequests, setSentRequests] = useState([]);
+  const [friends, setFriends] = useState([]);
+  const [email, setEmail] = useState(""); // 친구 요청할 이메일
+  const [page, setPage] = useState(0); // 친구 목록 페이지
+  const [hasNext, setHasNext] = useState(true); // 페이지네이션 상태
+  const [isLoading, setIsLoading] = useState(false);
+
+  // 탭 변경 함수
+  const switchTab = (tab) => setActiveTab(tab);
+
+  // 받은 친구 요청 조회
+  useEffect(() => {
+    const fetchReceivedRequests = async () => {
+      try {
+        const data = await getReceivedFriendRequests();
+        setReceivedRequests(data);
+      } catch (error) {
+        console.error("Failed to fetch received requests:", error);
+      }
+    };
+    if (activeTab === "friends") fetchReceivedRequests();
+  }, [activeTab]);
+
+  // 친구 목록 무한스크롤 처리
+  useEffect(() => {
+    const fetchFriends = async () => {
+      if (!hasNext || isLoading) return;
+      try {
+        setIsLoading(true);
+        const data = await getAllFriends(page, 10);
+        setFriends((prev) => [...prev, ...data.content]);
+        setHasNext(data.hasNext);
+        setPage((prev) => prev + 1);
+      } catch (error) {
+        console.error("Failed to fetch friends:", error);
+      } finally {
+        setIsLoading(false);
+      }
+    };
+    if (activeTab === "friends") fetchFriends();
+  }, [page, hasNext, activeTab]);
+
+  // 친구 요청 보내기
+  const handleSendRequest = async () => {
+    try {
+      const requestData = { email };
+      const response = await sendFriendRequest(requestData);
+      setSentRequests((prev) => [...prev, response.data]);
+      setEmail(""); // 입력 필드 초기화
+    } catch (error) {
+      console.error("Failed to send friend request:", error);
+    }
+  };
+
+  // 친구 요청 수락
+  const handleAcceptRequest = async (requestId) => {
+    try {
+      const response = await acceptFriendRequest(requestId);
+      setReceivedRequests((prev) =>
+        prev.filter((request) => request.id !== requestId)
+      );
+      setFriends((prev) => [response, ...prev]); // 친구 목록에 추가
+    } catch (error) {
+      console.error("Failed to accept request:", error);
+    }
+  };
+
+  // 친구 요청 거절
+  const handleRejectRequest = async (requestId) => {
+    try {
+      await rejectFriendRequest(requestId);
+      setReceivedRequests((prev) =>
+        prev.filter((request) => request.id !== requestId)
+      );
+    } catch (error) {
+      console.error("Failed to reject request:", error);
+    }
+  };
+
+  // 친구 삭제
+  const handleDeleteFriend = async (friendId) => {
+    try {
+      await deleteFriend(friendId);
+      setFriends((prev) => prev.filter((friend) => friend.id !== friendId));
+    } catch (error) {
+      console.error("Failed to delete friend:", error);
+    }
+  };
+
+  return (
+    <div className="w-full max-w-screen-lg min-h-screen mx-auto bg-white">
+      {/* 프로필 영역 */}
+      <div className="flex items-center px-6 py-4 border-b">
+        <div className="flex-shrink-0">
+          {/* <img
+            src={user?.profilePicture || "https://via.placeholder.com/150"}
+            alt="프로필 사진"
+            className="object-cover w-20 h-20 rounded-full"
+          /> */}
+          <LogoIcon className="w-20 h-20 rounded-full" />
+        </div>
+        <div className="flex-grow ml-6">
+          <h1 className="text-xl font-bold">{user?.name || "Guest"}</h1>
+          <p className="text-gray-600">{user?.email || "guest@example.com"}</p>
+        </div>
+      </div>
+
+      {/* 탭 네비게이션 */}
+      <div className="flex justify-center py-2 space-x-6 border-b">
+        <button
+          className={`px-4 py-2 text-sm font-medium ${
+            activeTab === "lightning"
+              ? "text-secondary-500 border-b-2 border-secondary-500"
+              : "text-gray-600"
+          }`}
+          onClick={() => switchTab("lightning")}
+        >
+          번개 채팅방
+        </button>
+        <button
+          className={`px-4 py-2 text-sm font-medium ${
+            activeTab === "friends"
+              ? "text-tertiary-500 border-b-2 border-tertiary-500"
+              : "text-gray-600"
+          }`}
+          onClick={() => switchTab("friends")}
+        >
+          친구
+        </button>
+      </div>
+
+      {/* 친구 탭 */}
+      {activeTab === "friends" && (
+        <div className="p-4 space-y-8">
+          {/* 받은 친구 요청 */}
+          <div>
+            <h2 className="text-lg font-bold">받은 친구 요청</h2>
+            <div className="space-y-4">
+              {receivedRequests.map((request) => (
+                <div
+                  key={request.id}
+                  className="flex items-center justify-between p-4 border rounded-lg"
+                >
+                  <div>
+                    <h3 className="font-semibold text-md">
+                      {request.requester.name}
+                    </h3>
+                    <p className="text-sm text-gray-600">
+                      {request.requester.email}
+                    </p>
+                  </div>
+                  <div className="space-x-2">
+                    <Button
+                      size="md"
+                      theme="indigo"
+                      onClick={() => handleAcceptRequest(request.id)}
+                    >
+                      수락
+                    </Button>
+                    <Button
+                      size="md"
+                      theme="pink"
+                      onClick={() => handleRejectRequest(request.id)}
+                    >
+                      거절
+                    </Button>
+                  </div>
+                </div>
+              ))}
+            </div>
+          </div>
+          <hr />
+          {/* 친구 요청하기 */}
+          <div>
+            <h2 className="text-lg font-bold">친구 요청하기</h2>
+            <div className="flex items-center my-4 space-x-4">
+              <input
+                type="email"
+                className="flex-1 min-w-0 p-3 border rounded-full"
+                placeholder="친구 이메일 입력"
+                value={email}
+                onChange={(e) => setEmail(e.target.value)}
+              />
+              <Button
+                size="md"
+                theme="indigo"
+                className="min-w-[50px]"
+                onClick={handleSendRequest}
+              >
+                요청
+              </Button>
+            </div>
+            <div className="mt-4 space-y-4">
+              {sentRequests.map((request) => (
+                <div key={request.id} className="p-2 border rounded-lg">
+                  <p className="text-sm">
+                    {request.receiver.name} ({request.receiver.email})
+                  </p>
+                  <p className="text-xs text-gray-500">{request.status}</p>
+                </div>
+              ))}
+            </div>
+          </div>
+          <hr />
+          {/* 친구 목록 */}
+          <div>
+            <h2 className="text-lg font-bold">친구 목록</h2>
+            <div className="space-y-4">
+              {friends.map((friend) => (
+                <div
+                  key={friend.id}
+                  className="flex items-center justify-between p-4 border rounded-lg"
+                >
+                  <div>
+                    <h3 className="font-semibold text-md">
+                      {friend.friendInfo.name}
+                    </h3>
+                    <p className="text-sm text-gray-600">
+                      {friend.friendInfo.email}
+                    </p>
+                  </div>
+                  <button
+                    className="px-4 py-2 text-sm text-white bg-red-500 rounded-lg hover:bg-red-600"
+                    onClick={() => handleDeleteFriend(friend.id)}
+                  >
+                    삭제
+                  </button>
+                </div>
+              ))}
+              {isLoading && <p>로딩 중...</p>}
+              {!hasNext && <p>더 이상 친구가 없습니다.</p>}
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  );
 };
 
 export default MyPage;
-- 
GitLab


From 573591e63a1b9c72ad75d9f95fea10f82a52c497 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sun, 8 Dec 2024 18:44:07 +0900
Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?=
 =?UTF-8?q?=EC=9D=B4=EC=A7=80=20meeting=20tab=20=EA=B0=9C=EB=B0=9C=20(#11)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/api/meeting.js       |  35 ++++++++++
 src/components/Card.jsx  |  20 ++++--
 src/components/Label.jsx |   2 +-
 src/pages/Mypage.jsx     | 147 ++++++++++++++++++++++++++-------------
 src/store/authStore.js   |   2 +-
 5 files changed, 150 insertions(+), 56 deletions(-)
 create mode 100644 src/api/meeting.js

diff --git a/src/api/meeting.js b/src/api/meeting.js
new file mode 100644
index 0000000..e4f7611
--- /dev/null
+++ b/src/api/meeting.js
@@ -0,0 +1,35 @@
+// src/api/meeting.js
+
+// 기본 API URL
+const BASE_URL = process.env.REACT_APP_BASE_URL;
+
+// 내가 참여하고 있는 채팅방 가져오기
+export const fetchMyMeetings = async (page = 0, size = 20) => {
+  try {
+    const response = await fetch(
+      `${BASE_URL}/api/meeting/my?page=${page}&size=${size}`,
+      {
+        method: "GET",
+        credentials: "include", // 세션 기반 인증을 위해 필요
+        headers: {
+          "Content-Type": "application/json",
+        },
+      }
+    );
+
+    if (!response.ok) {
+      throw new Error(`Error: ${response.status}`);
+    }
+
+    const result = await response.json();
+
+    if (!result.success) {
+      throw new Error("Failed to fetch meetings.");
+    }
+
+    return result.data; // 서버에서 제공된 데이터 반환
+  } catch (error) {
+    console.error("Error fetching my meetings:", error);
+    throw error;
+  }
+};
diff --git a/src/components/Card.jsx b/src/components/Card.jsx
index 7f936f0..b721751 100644
--- a/src/components/Card.jsx
+++ b/src/components/Card.jsx
@@ -1,6 +1,7 @@
 import { cn } from "../libs/index";
 import { cva } from "class-variance-authority";
 import React from "react";
+import Label from "./Label";
 
 // Card 스타일 변수를 정의합니다.
 const cardVariants = cva("w-full rounded-xl shadow-lg p-4 overflow-hidden", {
@@ -35,13 +36,20 @@ export default function Card({ meeting, theme = "black", onClick }) {
   return (
     <div className={cn(variantClass)} onClick={onClick}>
       <h3 className="mb-2 text-xl font-bold">{title}</h3>
-      <p className="text-sm text-gray-700">{location}</p>
-      <p className="text-base">
-        시간: {timeIdxStart} ~ {timeIdxEnd}
-      </p>
-      <p className="text-base">마감 시간: {time_idx_deadline}</p>
+      <div className="flex gap-2 mb-2">
+        <Label size="sm" theme="indigo">
+          {location}
+        </Label>
+        <Label size="sm" theme="indigo">
+          시간: {timeIdxStart} ~ {timeIdxEnd}
+        </Label>
+      </div>
+
+      <Label size="sm" theme="indigo">
+        마감 시간: {time_idx_deadline}
+      </Label>
       <div className="flex justify-between mt-2">
-        <span className="text-sm text-gray-600">작성자: {creatorName}</span>
+        <span className="text-sm text-white">작성자: {creatorName}</span>
         <span className="text-sm text-right">
           {type === "OPEN" ? "참여 가능" : "참여 마감"}
         </span>
diff --git a/src/components/Label.jsx b/src/components/Label.jsx
index 6fae129..8a2edf8 100644
--- a/src/components/Label.jsx
+++ b/src/components/Label.jsx
@@ -9,7 +9,7 @@ const labelVariants = cva(
       theme: {
         indigo: "bg-secondary-900 text-white",
         solid: "bg-primary-600 text-white",
-        lightsolid: "bg-primary-100 text-primary-600",
+        lightsolid: "bg-primary-600 text-primary-100 border border-primary-100",
         graysolid: "bg-grayscale-100 text-grayscale-900",
         ghost: "border border-grayscale-500 text-grayscale-900",
       },
diff --git a/src/pages/Mypage.jsx b/src/pages/Mypage.jsx
index d9eef68..d846cbc 100644
--- a/src/pages/Mypage.jsx
+++ b/src/pages/Mypage.jsx
@@ -10,6 +10,8 @@ import {
 } from "../api/friend";
 import Button from "../components/Button";
 import LogoIcon from "../components/icons/LogoIcon";
+import { fetchMyMeetings } from "../api/meeting";
+import Card from "../components/Card";
 
 const MyPage = () => {
   const { user } = useAuthStore(); // Zustand에서 user 상태 가져오기
@@ -22,9 +24,35 @@ const MyPage = () => {
   const [hasNext, setHasNext] = useState(true); // 페이지네이션 상태
   const [isLoading, setIsLoading] = useState(false);
 
+  const [meetings, setMeetings] = useState([]);
+  const [meetingPage, setMeetingPage] = useState(0);
+  const [meetingHasNext, setMeetingHasNext] = useState(true);
+  const [meetingIsLoading, setMeetingIsLoading] = useState(false);
+
   // 탭 변경 함수
   const switchTab = (tab) => setActiveTab(tab);
 
+  // 번개 모임 가져오기
+  useEffect(() => {
+    const fetchMeetings = async () => {
+      if (!meetingHasNext || meetingIsLoading) return;
+
+      try {
+        setMeetingIsLoading(true);
+        const data = await fetchMyMeetings(meetingPage, 20);
+        setMeetings((prev) => [...prev, ...data.content]);
+        setMeetingHasNext(data.meetingHasNext);
+        setMeetingPage((prev) => prev + 1);
+      } catch (error) {
+        console.error("Failed to fetch meetings:", error);
+      } finally {
+        setIsLoading(false);
+      }
+    };
+
+    if (activeTab === "lightning") fetchMeetings();
+  }, [activeTab, page, hasNext]);
+
   // 받은 친구 요청 조회
   useEffect(() => {
     const fetchReceivedRequests = async () => {
@@ -109,11 +137,6 @@ const MyPage = () => {
       {/* 프로필 영역 */}
       <div className="flex items-center px-6 py-4 border-b">
         <div className="flex-shrink-0">
-          {/* <img
-            src={user?.profilePicture || "https://via.placeholder.com/150"}
-            alt="프로필 사진"
-            className="object-cover w-20 h-20 rounded-full"
-          /> */}
           <LogoIcon className="w-20 h-20 rounded-full" />
         </div>
         <div className="flex-grow ml-6">
@@ -145,13 +168,69 @@ const MyPage = () => {
           친구
         </button>
       </div>
+      {/* 번개 모임 탭 */}
+      {activeTab === "lightning" && (
+        <div className="p-4">
+          {meetings.length === 0 && !isLoading && (
+            <p className="text-center">참여 중인 번개 모임이 없습니다.</p>
+          )}
+          <div className="grid grid-cols-1 gap-4 tablet:grid-cols-2 desktop:grid-cols-3">
+            {meetings.map((meeting) => (
+              <Card
+                key={meeting.id}
+                meeting={meeting}
+                theme="purple"
+                onClick={() => console.log("Clicked meeting:", meeting.id)}
+              />
+            ))}
+          </div>
+          {isLoading && <p className="text-center">로딩 중...</p>}
+          {!hasNext && meetings.length > 0 && (
+            <p className="text-sm text-center text-gray-500">
+              더 이상 불러올 번개 모임이 없습니다.
+            </p>
+          )}
+        </div>
+      )}
 
       {/* 친구 탭 */}
       {activeTab === "friends" && (
         <div className="p-4 space-y-8">
+          {/* 친구 요청하기 */}
+          <div>
+            <h2 className="text-lg font-bold">친구 요청하기</h2>
+            <div className="flex items-center my-4 space-x-4">
+              <input
+                type="email"
+                className="flex-1 min-w-0 p-3 border rounded-full"
+                placeholder="친구 이메일 입력"
+                value={email}
+                onChange={(e) => setEmail(e.target.value)}
+              />
+              <Button
+                size="md"
+                theme="indigo"
+                className="min-w-[50px]"
+                onClick={handleSendRequest}
+              >
+                요청
+              </Button>
+            </div>
+            <div className="mt-4 space-y-4">
+              {sentRequests.map((request) => (
+                <div key={request.id} className="p-2 border rounded-lg">
+                  <p className="text-sm">
+                    {request.receiver.name} ({request.receiver.email})
+                  </p>
+                  <p className="text-xs text-gray-500">{request.status}</p>
+                </div>
+              ))}
+            </div>
+          </div>
+          <hr />
           {/* 받은 친구 요청 */}
           <div>
-            <h2 className="text-lg font-bold">받은 친구 요청</h2>
+            <h2 className="mb-2 text-lg font-bold">받은 친구 요청</h2>
             <div className="space-y-4">
               {receivedRequests.map((request) => (
                 <div
@@ -162,20 +241,20 @@ const MyPage = () => {
                     <h3 className="font-semibold text-md">
                       {request.requester.name}
                     </h3>
-                    <p className="text-sm text-gray-600">
+                    <p className="text-[10px] tablet:text-sm text-gray-600">
                       {request.requester.email}
                     </p>
                   </div>
-                  <div className="space-x-2">
+                  <div className="flex gap-2">
                     <Button
-                      size="md"
+                      size="sm"
                       theme="indigo"
                       onClick={() => handleAcceptRequest(request.id)}
                     >
                       수락
                     </Button>
                     <Button
-                      size="md"
+                      size="sm"
                       theme="pink"
                       onClick={() => handleRejectRequest(request.id)}
                     >
@@ -187,41 +266,10 @@ const MyPage = () => {
             </div>
           </div>
           <hr />
-          {/* 친구 요청하기 */}
-          <div>
-            <h2 className="text-lg font-bold">친구 요청하기</h2>
-            <div className="flex items-center my-4 space-x-4">
-              <input
-                type="email"
-                className="flex-1 min-w-0 p-3 border rounded-full"
-                placeholder="친구 이메일 입력"
-                value={email}
-                onChange={(e) => setEmail(e.target.value)}
-              />
-              <Button
-                size="md"
-                theme="indigo"
-                className="min-w-[50px]"
-                onClick={handleSendRequest}
-              >
-                요청
-              </Button>
-            </div>
-            <div className="mt-4 space-y-4">
-              {sentRequests.map((request) => (
-                <div key={request.id} className="p-2 border rounded-lg">
-                  <p className="text-sm">
-                    {request.receiver.name} ({request.receiver.email})
-                  </p>
-                  <p className="text-xs text-gray-500">{request.status}</p>
-                </div>
-              ))}
-            </div>
-          </div>
-          <hr />
+
           {/* 친구 목록 */}
           <div>
-            <h2 className="text-lg font-bold">친구 목록</h2>
+            <h2 className="mb-2 text-lg font-bold">친구 목록</h2>
             <div className="space-y-4">
               {friends.map((friend) => (
                 <div
@@ -232,20 +280,23 @@ const MyPage = () => {
                     <h3 className="font-semibold text-md">
                       {friend.friendInfo.name}
                     </h3>
-                    <p className="text-sm text-gray-600">
+                    <p className="text-[10px] tablet:text-sm text-gray-600">
                       {friend.friendInfo.email}
                     </p>
                   </div>
-                  <button
-                    className="px-4 py-2 text-sm text-white bg-red-500 rounded-lg hover:bg-red-600"
+                  <Button
+                    size="sm"
+                    theme="black"
                     onClick={() => handleDeleteFriend(friend.id)}
                   >
                     삭제
-                  </button>
+                  </Button>
                 </div>
               ))}
               {isLoading && <p>로딩 중...</p>}
-              {!hasNext && <p>더 이상 친구가 없습니다.</p>}
+              {!hasNext && (
+                <p className="text-center label-2">더 이상 친구가 없습니다.</p>
+              )}
             </div>
           </div>
         </div>
diff --git a/src/store/authStore.js b/src/store/authStore.js
index 4e6141e..05da3b6 100644
--- a/src/store/authStore.js
+++ b/src/store/authStore.js
@@ -10,7 +10,7 @@ const useAuthStore = create((set) => ({
     set({ loading: true });
     try {
       const userInfo = await getSessionInfo();
-      set(userInfo);
+      set({ user: userInfo });
     } catch (error) {
       console.error("Failed to fetch session info:", error);
       set({ user: null });
-- 
GitLab


From ce803830fb69f61c7f5f8af608b3f0f9457d3634 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sun, 8 Dec 2024 18:50:10 +0900
Subject: [PATCH 08/10] =?UTF-8?q?design:=20=EB=A7=88=EC=9D=B4=ED=8E=98?=
 =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=83=AD=20=EC=9D=B4=EB=A6=84=20=EB=B3=80?=
 =?UTF-8?q?=EA=B2=BD=20(#11)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/pages/Mypage.jsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/Mypage.jsx b/src/pages/Mypage.jsx
index d846cbc..29680f9 100644
--- a/src/pages/Mypage.jsx
+++ b/src/pages/Mypage.jsx
@@ -155,7 +155,7 @@ const MyPage = () => {
           }`}
           onClick={() => switchTab("lightning")}
         >
-          번개 채팅방
+          번개 모임
         </button>
         <button
           className={`px-4 py-2 text-sm font-medium ${
-- 
GitLab


From dbd016bcf931fa7355dc768728c47d7d174de30e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sun, 8 Dec 2024 19:10:24 +0900
Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=EB=B9=84=EB=A1=9C=EA=B7=B8?=
 =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=20=EC=A0=91=EA=B7=BC=20=EC=B0=A8=EB=8B=A8?=
 =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20(#11)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/pages/Mypage.jsx   | 22 +++++++++++++++++++++-
 src/store/authStore.js |  2 ++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/src/pages/Mypage.jsx b/src/pages/Mypage.jsx
index 29680f9..68aa459 100644
--- a/src/pages/Mypage.jsx
+++ b/src/pages/Mypage.jsx
@@ -12,9 +12,10 @@ import Button from "../components/Button";
 import LogoIcon from "../components/icons/LogoIcon";
 import { fetchMyMeetings } from "../api/meeting";
 import Card from "../components/Card";
+import { useNavigate } from "react-router-dom";
 
 const MyPage = () => {
-  const { user } = useAuthStore(); // Zustand에서 user 상태 가져오기
+  const { user, fetchSession } = useAuthStore(); // Zustand 상태 및 메서드 가져오기
   const [activeTab, setActiveTab] = useState("lightning"); // 현재 활성화된 탭
   const [receivedRequests, setReceivedRequests] = useState([]);
   const [sentRequests, setSentRequests] = useState([]);
@@ -29,9 +30,28 @@ const MyPage = () => {
   const [meetingHasNext, setMeetingHasNext] = useState(true);
   const [meetingIsLoading, setMeetingIsLoading] = useState(false);
 
+  const navigate = useNavigate();
+
   // 탭 변경 함수
   const switchTab = (tab) => setActiveTab(tab);
 
+  useEffect(() => {
+    const fetchUserSession = async () => {
+      try {
+        const userInfo = localStorage.getItem("user");
+        if (!userInfo) {
+          alert("로그인이 필요한 페이지입니다.");
+          navigate("/login");
+        }
+        await fetchSession(); // 세션 정보 가져오기
+      } catch (error) {
+        console.error("Failed to fetch session:", error);
+      }
+    };
+
+    fetchUserSession();
+  }, [fetchSession]); // 페이지 마운트 시 실행
+
   // 번개 모임 가져오기
   useEffect(() => {
     const fetchMeetings = async () => {
diff --git a/src/store/authStore.js b/src/store/authStore.js
index 05da3b6..5715ec0 100644
--- a/src/store/authStore.js
+++ b/src/store/authStore.js
@@ -11,6 +11,7 @@ const useAuthStore = create((set) => ({
     try {
       const userInfo = await getSessionInfo();
       set({ user: userInfo });
+      localStorage.setItem("user", { user: userInfo });
     } catch (error) {
       console.error("Failed to fetch session info:", error);
       set({ user: null });
@@ -24,6 +25,7 @@ const useAuthStore = create((set) => ({
     try {
       await logout();
       set({ user: null });
+      localStorage.removeItem("user");
     } catch (error) {
       console.error("Failed to logout:", error);
     }
-- 
GitLab


From 1e6d85262313d64854a13c46d56a5e3c351fba8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=84=9D=EC=B0=AC=20=EC=9C=A4?= <ysc0731@ajou.ac.kr>
Date: Sun, 8 Dec 2024 19:14:28 +0900
Subject: [PATCH 10/10] =?UTF-8?q?fix:=20useEffect=20dependency=20=EC=98=A4?=
 =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95=20(#11)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/pages/Mypage.jsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/pages/Mypage.jsx b/src/pages/Mypage.jsx
index 68aa459..2c8c013 100644
--- a/src/pages/Mypage.jsx
+++ b/src/pages/Mypage.jsx
@@ -50,7 +50,7 @@ const MyPage = () => {
     };
 
     fetchUserSession();
-  }, [fetchSession]); // 페이지 마운트 시 실행
+  }, [fetchSession, navigate]); // 페이지 마운트 시 실행
 
   // 번개 모임 가져오기
   useEffect(() => {
@@ -71,7 +71,7 @@ const MyPage = () => {
     };
 
     if (activeTab === "lightning") fetchMeetings();
-  }, [activeTab, page, hasNext]);
+  }, [activeTab, meetingPage, meetingHasNext, meetingIsLoading]);
 
   // 받은 친구 요청 조회
   useEffect(() => {
@@ -103,7 +103,7 @@ const MyPage = () => {
       }
     };
     if (activeTab === "friends") fetchFriends();
-  }, [page, hasNext, activeTab]);
+  }, [page, hasNext, activeTab, isLoading]);
 
   // 친구 요청 보내기
   const handleSendRequest = async () => {
-- 
GitLab