From c4571ab9fd99fd98ddffcd11cdd5aa5b562ce93d 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: Fri, 6 Dec 2024 02:38:44 +0900
Subject: [PATCH 1/6] =?UTF-8?q?[#8]=20=EC=8A=A4=EC=BC=80=EC=A4=84=20?=
 =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B0=9C=EB=B0=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitignore                  |   1 +
 src/App.js                  |   4 +-
 src/api/schedule.js         | 157 ++++++++++++++
 src/pages/Schedule.jsx      |   7 -
 src/pages/SchedulePage.jsx  | 411 ++++++++++++++++++++++++++++++++++++
 src/pages/TimeTablePage.jsx |   7 -
 6 files changed, 571 insertions(+), 16 deletions(-)
 create mode 100644 src/api/schedule.js
 delete mode 100644 src/pages/Schedule.jsx
 create mode 100644 src/pages/SchedulePage.jsx
 delete mode 100644 src/pages/TimeTablePage.jsx

diff --git a/.gitignore b/.gitignore
index 4d29575..8692cf6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
 
 # misc
 .DS_Store
+.env
 .env.local
 .env.development.local
 .env.test.local
diff --git a/src/App.js b/src/App.js
index 689e5d3..43a9c0d 100644
--- a/src/App.js
+++ b/src/App.js
@@ -4,13 +4,13 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
 import LoginPage from "./pages/LoginPage";
 import SignupPage from "./pages/SignUpPage";
 import HomePage from "./pages/HomePage";
-import TimeTablePage from "./pages/TimeTablePage";
 import ChattingListPage from "./pages/Chatting/ChattingListPage";
 import MyPage from "./pages/Mypage";
 import HeaderNav from "./components/layout/HeaderNav";
 import Footer from "./components/layout/Footer";
 import BodyLayout from "./components/layout/BodyLayout";
 import HeaderLogoBar from "./components/layout/HeaderLogoBar";
+import SchedulePage from "./pages/SchedulePage";
 
 const App = () => {
   return (
@@ -21,7 +21,7 @@ const App = () => {
         <BodyLayout>
           <Routes>
             <Route path="/" element={<HomePage />} />
-            <Route path="/timetable" element={<TimeTablePage />} />
+            <Route path="/timetable" element={<SchedulePage />} />
             <Route path="/chattinglist" element={<ChattingListPage />} />
             <Route path="/mypage" element={<MyPage />} />
             <Route path="/login" element={<LoginPage />} />
diff --git a/src/api/schedule.js b/src/api/schedule.js
new file mode 100644
index 0000000..bb36ad4
--- /dev/null
+++ b/src/api/schedule.js
@@ -0,0 +1,157 @@
+// api.js
+const baseURL = process.env.REACT_APP_BACKEND_BASE_URL;
+
+// Fetch all schedules
+export const fetchAllSchedules = async () => {
+  try {
+    const response = await fetch(`${baseURL}/api/schedule/all`, {
+      method: "GET",
+      credentials: "include", // Include credentials for session-based authentication
+      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 schedules.");
+    }
+
+    return result.data.schedules;
+  } catch (error) {
+    console.error("Error fetching schedules:", error);
+    throw error;
+  }
+};
+
+// Fetch schedule by time index
+export const fetchScheduleByTimeIndex = async (timeIdx) => {
+  try {
+    const response = await fetch(`${baseURL}/api/schedule/${timeIdx}`, {
+      method: "GET",
+      credentials: "include", // Include credentials for session-based authentication
+      headers: {
+        "Content-Type": "application/json",
+      },
+    });
+
+    if (!response.ok) {
+      if (response.status === 404) {
+        const error = await response.json();
+        throw new Error(error.error.message || "Schedule not found.");
+      }
+      throw new Error(`Error: ${response.status}`);
+    }
+
+    const result = await response.json();
+
+    if (!result.success) {
+      throw new Error("Failed to fetch schedule.");
+    }
+
+    return result.data.schedule;
+  } catch (error) {
+    console.error(`Error fetching schedule with timeIdx ${timeIdx}:`, error);
+    throw error;
+  }
+};
+
+// Create a new schedule
+export const createSchedule = async (scheduleData) => {
+  try {
+    const response = await fetch(`${baseURL}/api/schedule`, {
+      method: "POST",
+      credentials: "include", // Include credentials for session-based authentication
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify(scheduleData),
+    });
+
+    if (!response.ok) {
+      if (response.status === 400) {
+        const error = await response.json();
+        throw new Error(error.error.message || "Failed to create schedule.");
+      }
+      throw new Error(`Error: ${response.status}`);
+    }
+
+    const result = await response.json();
+
+    if (!result.success) {
+      throw new Error("Failed to create schedule.");
+    }
+
+    return result.data.schedule;
+  } catch (error) {
+    console.error("Error creating schedule:", error);
+    throw error;
+  }
+};
+
+// Update an existing schedule
+export const updateSchedule = async (scheduleData) => {
+  try {
+    const response = await fetch(`${baseURL}/api/schedule`, {
+      method: "PUT",
+      credentials: "include", // Include credentials for session-based authentication
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify(scheduleData),
+    });
+
+    if (!response.ok) {
+      throw new Error(`Error: ${response.status}`);
+    }
+
+    const result = await response.json();
+
+    if (!result.success) {
+      throw new Error("Failed to update schedule.");
+    }
+
+    return result.data.schedule;
+  } catch (error) {
+    console.error("Error updating schedule:", error);
+    throw error;
+  }
+};
+
+// Delete a schedule
+export const deleteSchedule = async (title) => {
+  try {
+    const response = await fetch(`${baseURL}/api/schedule`, {
+      method: "DELETE",
+      credentials: "include", // Include credentials for session-based authentication
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify({ title }),
+    });
+
+    if (!response.ok) {
+      if (response.status === 404) {
+        const error = await response.json();
+        throw new Error(error.error.message || "Schedule not found.");
+      }
+      throw new Error(`Error: ${response.status}`);
+    }
+
+    const result = await response.json();
+
+    if (!result.success) {
+      throw new Error("Failed to delete schedule.");
+    }
+
+    return result.data;
+  } catch (error) {
+    console.error("Error deleting schedule:", error);
+    throw error;
+  }
+};
diff --git a/src/pages/Schedule.jsx b/src/pages/Schedule.jsx
deleted file mode 100644
index 980b0e5..0000000
--- a/src/pages/Schedule.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from "react";
-
-const Schedule = () => {
-  return <></>;
-};
-
-export default Schedule;
diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx
new file mode 100644
index 0000000..5fa5f28
--- /dev/null
+++ b/src/pages/SchedulePage.jsx
@@ -0,0 +1,411 @@
+import React, { useEffect, useState } from "react";
+import Label from "../components/Label";
+import {
+  createSchedule,
+  deleteSchedule,
+  fetchScheduleByTimeIndex,
+  fetchAllSchedules,
+  updateSchedule,
+} from "../api/schedule";
+
+const generateTimeSlots = () => {
+  const timeSlots = [];
+  for (let hour = 0; hour < 24; hour++) {
+    for (let min = 0; min < 60; min += 15) {
+      timeSlots.push(
+        `${hour.toString().padStart(2, "0")}:${min.toString().padStart(2, "0")}`
+      );
+    }
+  }
+  return timeSlots;
+};
+
+const days = ["월", "화", "수", "목", "금", "토", "일"];
+
+// const dummySchedules = [
+//   {
+//     id: 1,
+//     user_id: 1,
+//     title: "알고리즘 스터디",
+//     is_fixed: true,
+//     time_indices: [36, 37, 38, 39],
+//     createdAt: "2024-12-02T09:52:00.000Z",
+//     updatedAt: "2024-12-02T09:52:00.000Z",
+//   },
+//   {
+//     id: 5,
+//     user_id: 1,
+//     title: "웹시설 팀플",
+//     is_fixed: true,
+//     time_indices: [165, 166, 167, 255, 256, 257],
+//     createdAt: "2024-12-02T09:54:53.000Z",
+//     updatedAt: "2024-12-02T09:54:53.000Z",
+//   },
+// ];
+
+const SchedulePage = () => {
+  const timeSlots = generateTimeSlots();
+  const [schedules, setSchedules] = useState([]);
+  const [isEditMode, setIsEditMode] = useState(false);
+  const [isUpdateMode, setIsUpdateMode] = useState(false);
+  const [selectedSchedule, setSelectedSchedule] = useState(null);
+  const [selectedSlots, setSelectedSlots] = useState([]);
+  const [newTitle, setNewTitle] = useState("");
+  const [isFixed, setIsFixed] = useState(true);
+
+  useEffect(() => {
+    // API
+    const initializeSchedules = async () => {
+      try {
+        const data = await fetchAllSchedules();
+        setSchedules(data);
+      } catch (error) {
+        console.error("Failed to load schedules", error);
+      }
+    };
+
+    initializeSchedules();
+
+    // 임시 코드
+    // setSchedules(dummySchedules);
+  }, []);
+
+  const handleSlotClick = async (timeIdx) => {
+    if (!isEditMode) return;
+
+    // API
+    try {
+      const response = await fetchScheduleByTimeIndex(timeIdx);
+      if (response && response.data && response.data.schedule) {
+        setSelectedSchedule(response.data.schedule); // API로 가져온 스케줄 설정
+      } else {
+        console.error("No schedule found for time index:", timeIdx);
+      }
+    } catch (error) {
+      console.error("Failed to fetch schedule for time index:", timeIdx, error);
+    }
+
+    // 임시 코드
+    // const slotInSchedule = schedules.find((s) =>
+    //   s.time_indices.includes(timeIdx)
+    // );
+
+    // if (slotInSchedule) {
+    //   if (selectedSlots.length === 0) {
+    //     setSelectedSchedule(slotInSchedule);
+    //   }
+    //   return;
+    // }
+
+    // if (selectedSlots.includes(timeIdx)) {
+    //   setSelectedSlots((prev) => prev.filter((idx) => idx !== timeIdx));
+    // } else {
+    //   setSelectedSlots((prev) => [...prev, timeIdx]);
+    // }
+  };
+
+  const handleCancelSchedule = () => {
+    setSelectedSlots([]);
+    setNewTitle("");
+    setIsFixed(true);
+    setSelectedSchedule(null);
+  };
+
+  const handleEditSchedule = () => {
+    setIsUpdateMode(true);
+    setSchedules((prev) =>
+      prev.filter((s) => s.title !== selectedSchedule.title)
+    );
+    setSelectedSlots(selectedSchedule.time_indices);
+    setNewTitle(selectedSchedule.title);
+    setIsFixed(selectedSchedule.is_fixed);
+  };
+
+  const handleUpdateSchedule = async () => {
+    try {
+      const scheduleData = {
+        originalTitle: selectedSchedule.title,
+        title: newTitle,
+        is_fixed: isFixed,
+        time_indices: selectedSlots,
+      };
+      // API
+      const newSchedule = await updateSchedule(scheduleData);
+
+      // 임시 코드
+      // const newSchedule = {
+      //   ...scheduleData,
+      //   id: Date.now(),
+      //   createdAt: new Date().toISOString(),
+      //   updatedAt: new Date().toISOString(),
+      // };
+
+      setSchedules((prev) => [...prev, newSchedule]);
+      setSelectedSchedule(newSchedule);
+      setSelectedSlots([]);
+      setNewTitle("");
+      setIsFixed(true);
+      alert("스케줄을 수정했습니다.!");
+    } catch (error) {
+      console.error("스케줄 수정에 실패했습니다.:", error);
+      alert("스케줄 수정에 실패했습니다.");
+    } finally {
+      setIsUpdateMode(false);
+    }
+  };
+
+  const handleCreateSchedule = async () => {
+    try {
+      const scheduleData = {
+        title: newTitle,
+        is_fixed: isFixed,
+        time_indices: selectedSlots,
+      };
+
+      // API
+      const newSchedule = await createSchedule(scheduleData);
+
+      // 임시코드
+      // const newSchedule = {
+      //   ...scheduleData,
+      //   id: Date.now(),
+      //   createdAt: new Date().toISOString(),
+      //   updatedAt: new Date().toISOString(),
+      // };
+
+      setSchedules((prev) => [...prev, newSchedule]);
+      setSelectedSlots([]);
+      setNewTitle("");
+      setIsFixed(true);
+      alert("스케줄이 추가되었습니다!");
+    } catch (error) {
+      console.error("스케줄 삭제에 실패했습니다:", error);
+      alert("스케줄 추가에 실패했습니다.");
+    }
+  };
+
+  const handleDeleteSchedule = async () => {
+    if (!selectedSchedule) return;
+
+    try {
+      const body = { title: selectedSchedule.title };
+
+      // API 호출 준비가 되었을 때 사용:
+      await deleteSchedule(body);
+
+      const updatedSchedules = await fetchAllSchedules();
+      setSchedules(updatedSchedules);
+
+      // 임시코드
+      // setSchedules((prev) =>
+      //   prev.filter((s) => s.title !== selectedSchedule.title)
+      // );
+
+      setSelectedSchedule(null);
+      alert("스케줄이 삭제되었습니다.");
+    } catch (error) {
+      console.error("스케줄 삭제에 실패했습니다:", error);
+    }
+  };
+
+  return (
+    <div className="min-h-screen bg-grayscale-50">
+      {/* Toggle View/Edit Mode */}
+      <div className="flex items-center justify-between p-4 bg-white shadow">
+        <h1 className="heading-1">Schedule</h1>
+        <label className="flex items-center space-x-3 cursor-pointer">
+          <span className="title-1 text-primary-500">Edit Mode</span>
+          <div
+            className={`relative w-12 h-6 rounded-full transition-colors ${
+              isEditMode ? "bg-primary-500" : "bg-grayscale-300"
+            }`}
+            onClick={() => {
+              setIsEditMode((prev) => !prev);
+              setSelectedSlots([]);
+              setSelectedSchedule(null);
+            }}
+          >
+            <div
+              className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white transition-transform ${
+                isEditMode ? "translate-x-6" : ""
+              }`}
+            ></div>
+          </div>
+        </label>
+      </div>
+
+      {/* Sticky Container in Edit Mode */}
+      {isEditMode && (
+        <div className="fixed bottom-0 right-0 flex items-center justify-center w-full ">
+          <div
+            className={`transform transition-transform w-full max-w-[768px] tablet:rounded-2xl bg-primary-100 p-6 text-center shadow-lg`}
+          >
+            {selectedSlots.length === 0 && selectedSchedule ? (
+              <div className="flex flex-col items-center justify-center w-full">
+                <h3 className="mb-2 heading-2 text-primary-500">스케줄 정보</h3>
+                <div className="flex flex-col items-start w-1/2">
+                  <p className="mb-1 body-1">
+                    <strong>제목:</strong> {selectedSchedule.title}
+                  </p>
+                  <p className="mb-1 body-1">
+                    <strong>스케줄 타입:</strong>{" "}
+                    {selectedSchedule.is_fixed ? "고정" : "유동"}
+                  </p>
+                  <div className="mb-4 body-1">
+                    <strong>선택된 시간:</strong>{" "}
+                    {selectedSchedule.time_indices.map((time_idx) => (
+                      <Label key={time_idx} theme="indigo" size="sm">
+                        {time_idx}
+                      </Label>
+                    ))}
+                  </div>
+                </div>
+                <div className="flex justify-center mt-4 space-x-4">
+                  <button
+                    className="px-4 py-2 font-bold text-white rounded bg-gradient-purple"
+                    onClick={handleEditSchedule}
+                  >
+                    수정하기
+                  </button>
+                  <button
+                    className="px-4 py-2 font-bold text-white rounded bg-gradient-pink"
+                    onClick={handleDeleteSchedule}
+                  >
+                    삭제하기
+                  </button>
+                </div>
+              </div>
+            ) : (
+              <>
+                {isUpdateMode ? (
+                  <h3 className="mb-4 heading-2 text-primary-500">
+                    스케줄 수정하기
+                  </h3>
+                ) : (
+                  <h3 className="mb-4 heading-2 text-primary-500">
+                    새 스케줄 만들기
+                  </h3>
+                )}
+                <input
+                  type="text"
+                  value={newTitle}
+                  onChange={(e) => setNewTitle(e.target.value)}
+                  placeholder="Enter title"
+                  className="w-full p-2 mb-4 border rounded shadow-input-box"
+                />
+                <div className="flex items-center justify-center mb-4 space-x-4">
+                  <label className="flex items-center space-x-2">
+                    <input
+                      type="radio"
+                      name="is_fixed"
+                      value={true}
+                      checked={isFixed === true}
+                      onChange={() => setIsFixed(true)}
+                    />
+                    <span className="body-1">고정 스케줄</span>
+                  </label>
+                  <label className="flex items-center space-x-2">
+                    <input
+                      type="radio"
+                      name="is_fixed"
+                      value={false}
+                      checked={isFixed === false}
+                      onChange={() => setIsFixed(false)}
+                    />
+                    <span className="body-1">유동 스케줄</span>
+                  </label>
+                </div>
+                <div className="mb-4 body-1">
+                  <span>선택된 시간:</span>
+                  {selectedSlots.map((time_idx) => (
+                    <Label key={time_idx} theme="indigo" size="sm">
+                      {time_idx}
+                    </Label>
+                  ))}
+                </div>
+                {isUpdateMode ? (
+                  <button
+                    className="px-4 py-2 font-bold text-white rounded bg-gradient-pink"
+                    onClick={() => handleUpdateSchedule()}
+                  >
+                    수정 완료
+                  </button>
+                ) : (
+                  <div className="flex justify-center mt-4 space-x-4">
+                    <button
+                      className="px-4 py-2 font-bold text-white rounded bg-tertiary-900"
+                      onClick={() => handleCancelSchedule()}
+                    >
+                      취소
+                    </button>
+                    <button
+                      className="px-4 py-2 font-bold text-white rounded bg-gradient-pink"
+                      onClick={() => handleCreateSchedule()}
+                    >
+                      추가
+                    </button>
+                  </div>
+                )}
+              </>
+            )}
+          </div>
+        </div>
+      )}
+
+      {/* Schedule Grid */}
+      <div className="p-4">
+        <div className="overflow-auto scrollbar-hide">
+          <div className="w-[100vw] tablet:w-[960px] grid grid-cols-[64px,repeat(7,1fr)] gap-2">
+            {/* Header */}
+            <div className="min-w-[54px] p-2 font-bold text-center bg-grayscale-200 select-none">
+              Time
+            </div>
+            {days.map((day) => (
+              <div
+                key={day}
+                className="p-2 font-bold text-center select-none bg-grayscale-200"
+              >
+                {day}
+              </div>
+            ))}
+
+            {/* Time Slots */}
+            {timeSlots.map((time, rowIndex) => (
+              <React.Fragment key={rowIndex}>
+                {/* Time Column */}
+                <div className="min-w-[54px] p-2 font-bold text-center bg-grayscale-100 select-none">
+                  {time}
+                </div>
+                {days.map((_, colIndex) => {
+                  const slotIndex = colIndex * timeSlots.length + rowIndex;
+                  const isSelected = selectedSlots.includes(slotIndex);
+                  const schedule = schedules.find((s) =>
+                    s.time_indices.includes(slotIndex)
+                  );
+
+                  return (
+                    <div
+                      key={slotIndex}
+                      className={`p-2 border rounded ${
+                        schedule
+                          ? "bg-primary-300 text-white cursor-not-allowed"
+                          : isSelected
+                          ? "bg-primary-100 border-primary-300"
+                          : "bg-grayscale-50 cursor-pointer"
+                      }`}
+                      onClick={() => handleSlotClick(slotIndex)}
+                    >
+                      {schedule ? schedule.title : ""}
+                    </div>
+                  );
+                })}
+              </React.Fragment>
+            ))}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default SchedulePage;
diff --git a/src/pages/TimeTablePage.jsx b/src/pages/TimeTablePage.jsx
deleted file mode 100644
index 059584d..0000000
--- a/src/pages/TimeTablePage.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from "react";
-
-const TimeTablePage = () => {
-  return <></>;
-};
-
-export default TimeTablePage;
-- 
GitLab


From 6a641de707e6dd9f81c3236ef1901e56afac9218 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 16:37:55 +0900
Subject: [PATCH 2/6] =?UTF-8?q?[#10]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?=
 =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B0=9C=EB=B0=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 package-lock.json                       | 31 ++++++++-
 package.json                            |  3 +-
 src/App.js                              |  2 -
 src/api/auth.js                         | 54 +++++++++++++++
 src/api/schedule.js                     |  2 +-
 src/components/icons/GoogleLogoIcon.jsx | 28 ++++++++
 src/components/layout/HeaderLogoBar.jsx | 14 +++-
 src/components/layout/HeaderNav.jsx     | 42 +++++++-----
 src/pages/LoginPage.jsx                 | 89 ++++++++++++++++++++++++-
 src/pages/SignUpPage.jsx                |  7 --
 src/store/authStore.js                  | 30 +++++++++
 11 files changed, 270 insertions(+), 32 deletions(-)
 create mode 100644 src/api/auth.js
 create mode 100644 src/components/icons/GoogleLogoIcon.jsx
 delete mode 100644 src/pages/SignUpPage.jsx
 create mode 100644 src/store/authStore.js

diff --git a/package-lock.json b/package-lock.json
index d9e2f70..671b138 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -30,7 +30,8 @@
         "workbox-range-requests": "^6.6.0",
         "workbox-routing": "^6.6.0",
         "workbox-strategies": "^6.6.0",
-        "workbox-streams": "^6.6.0"
+        "workbox-streams": "^6.6.0",
+        "zustand": "^5.0.2"
       },
       "devDependencies": {
         "tailwindcss": "^3.4.15"
@@ -16441,6 +16442,34 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
+    },
+    "node_modules/zustand": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.2.tgz",
+      "integrity": "sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==",
+      "engines": {
+        "node": ">=12.20.0"
+      },
+      "peerDependencies": {
+        "@types/react": ">=18.0.0",
+        "immer": ">=9.0.6",
+        "react": ">=18.0.0",
+        "use-sync-external-store": ">=1.2.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "immer": {
+          "optional": true
+        },
+        "react": {
+          "optional": true
+        },
+        "use-sync-external-store": {
+          "optional": true
+        }
+      }
     }
   }
 }
diff --git a/package.json b/package.json
index 032f68f..a8aeba3 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,8 @@
     "workbox-range-requests": "^6.6.0",
     "workbox-routing": "^6.6.0",
     "workbox-strategies": "^6.6.0",
-    "workbox-streams": "^6.6.0"
+    "workbox-streams": "^6.6.0",
+    "zustand": "^5.0.2"
   },
   "scripts": {
     "start": "react-scripts start",
diff --git a/src/App.js b/src/App.js
index 43a9c0d..af5e2cb 100644
--- a/src/App.js
+++ b/src/App.js
@@ -2,7 +2,6 @@ import React from "react";
 import "./styles/globals.css";
 import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
 import LoginPage from "./pages/LoginPage";
-import SignupPage from "./pages/SignUpPage";
 import HomePage from "./pages/HomePage";
 import ChattingListPage from "./pages/Chatting/ChattingListPage";
 import MyPage from "./pages/Mypage";
@@ -25,7 +24,6 @@ const App = () => {
             <Route path="/chattinglist" element={<ChattingListPage />} />
             <Route path="/mypage" element={<MyPage />} />
             <Route path="/login" element={<LoginPage />} />
-            <Route path="/signup" element={<SignupPage />} />
           </Routes>
         </BodyLayout>
         <Footer />
diff --git a/src/api/auth.js b/src/api/auth.js
new file mode 100644
index 0000000..df4042f
--- /dev/null
+++ b/src/api/auth.js
@@ -0,0 +1,54 @@
+// src/api/auth.js
+
+/**
+ * Google 로그인 URL 반환
+ * @returns {string} 로그인 엔드포인트 URL
+ */
+export const getLoginUrl = () => {
+  return `${process.env.REACT_APP_BASE_URL}/api/auth/login`;
+};
+
+/**
+ * 로그아웃 API 호출
+ * @returns {Promise<void>}
+ */
+export const logout = async () => {
+  try {
+    const response = await fetch(
+      `${process.env.REACT_APP_BASE_URL}/api/auth/logout`,
+      {
+        method: "GET",
+        credentials: "include", // 세션 쿠키 포함
+      }
+    );
+    if (!response.ok) {
+      throw new Error("Failed to logout");
+    }
+  } catch (error) {
+    console.error("Error during logout:", error);
+    throw error;
+  }
+};
+
+/**
+ * 세션 정보 확인 API 호출
+ * @returns {Promise<Object|null>} 세션이 있으면 사용자 정보 반환, 없으면 null 반환
+ */
+export const getSessionInfo = async () => {
+  try {
+    const response = await fetch(
+      `${process.env.REACT_APP_BASE_URL}/api/session/info`,
+      {
+        method: "GET",
+        credentials: "include", // 세션 쿠키 포함
+      }
+    );
+    if (response.ok) {
+      return await response.json(); // 사용자 정보 반환
+    }
+    return null; // 세션 없음
+  } catch (error) {
+    console.error("Error checking session info:", error);
+    throw error;
+  }
+};
diff --git a/src/api/schedule.js b/src/api/schedule.js
index bb36ad4..c59a852 100644
--- a/src/api/schedule.js
+++ b/src/api/schedule.js
@@ -1,5 +1,5 @@
 // api.js
-const baseURL = process.env.REACT_APP_BACKEND_BASE_URL;
+const baseURL = process.env.REACT_APP_BASE_URL;
 
 // Fetch all schedules
 export const fetchAllSchedules = async () => {
diff --git a/src/components/icons/GoogleLogoIcon.jsx b/src/components/icons/GoogleLogoIcon.jsx
new file mode 100644
index 0000000..a28ef03
--- /dev/null
+++ b/src/components/icons/GoogleLogoIcon.jsx
@@ -0,0 +1,28 @@
+export default function GoogleLogo({ className, ...props }) {
+  return (
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
+      width="20"
+      height="20"
+      viewBox="0 0 20 20"
+      fill="none"
+    >
+      <path
+        d="M16.5883 17.58C18.951 15.3748 20.0011 11.6995 19.371 8.18164H9.97266V12.067H15.3281C15.1181 13.3271 14.3831 14.3772 13.333 15.0598L16.5883 17.58Z"
+        fill="#4285F4"
+      />
+      <path
+        d="M1.04688 14.4824C1.73758 15.843 2.72813 17.0291 3.94387 17.9512C5.15962 18.8733 6.56882 19.5074 8.06526 19.8056C9.5617 20.1039 11.1063 20.0586 12.5827 19.6731C14.0591 19.2876 15.4287 18.572 16.5883 17.5802L13.333 15.06C10.5502 16.8976 5.92982 16.2151 4.35467 11.9097L1.04688 14.4824Z"
+        fill="#34A853"
+      />
+      <path
+        d="M4.35584 11.9095C3.93581 10.5969 3.93581 9.38926 4.35584 8.07664L1.04804 5.50391C-0.159565 7.91912 -0.527099 11.3319 1.04804 14.4822L4.35584 11.9095Z"
+        fill="#FBBC02"
+      />
+      <path
+        d="M4.35467 8.07674C5.50978 4.45391 10.4452 2.35372 13.753 5.4515L16.6408 2.61624C12.5454 -1.32161 4.56469 -1.1641 1.04688 5.50401L4.35467 8.07674Z"
+        fill="#EA4335"
+      />
+    </svg>
+  );
+}
diff --git a/src/components/layout/HeaderLogoBar.jsx b/src/components/layout/HeaderLogoBar.jsx
index 0b8aef8..4e08f2d 100644
--- a/src/components/layout/HeaderLogoBar.jsx
+++ b/src/components/layout/HeaderLogoBar.jsx
@@ -1,12 +1,24 @@
+import React from "react";
 import LogoIcon from "../icons/LogoIcon";
+import useAuthStore from "../../store/authStore";
 
 const HeaderLogoBar = () => {
+  const { user } = useAuthStore(); // Zustand에서 user 상태 가져오기
+
   return (
-    <div className="flex items-center justify-start w-full h-16 px-4 bg-white">
+    <div className="flex items-center justify-between w-full h-16 px-4 bg-white">
+      {/* 왼쪽: 로고와 앱 이름 */}
       <div className="flex items-center">
         <LogoIcon width={32} height={32} />
         <span className="title-1">YANAWA</span>
       </div>
+
+      {/* 오른쪽: 사용자 이름 */}
+      <div className="flex items-center">
+        <span className="text-gray-600 label-1">
+          {user ? `${user.name}` : "guest"} 님
+        </span>
+      </div>
     </div>
   );
 };
diff --git a/src/components/layout/HeaderNav.jsx b/src/components/layout/HeaderNav.jsx
index e1d5d5d..2dba5ad 100644
--- a/src/components/layout/HeaderNav.jsx
+++ b/src/components/layout/HeaderNav.jsx
@@ -2,10 +2,12 @@ import React, { useState, useEffect } from "react";
 import { useNavigate } from "react-router-dom";
 import Button from "../Button";
 import LogoIcon from "../icons/LogoIcon";
+import useAuthStore from "../../store/authStore";
 
 export default function HeaderNav() {
   const navigate = useNavigate();
   const [isMobile, setIsMobile] = useState(false);
+  const { user } = useAuthStore(); // Zustand에서 user 상태 가져오기
 
   useEffect(() => {
     const checkMobile = () => setIsMobile(window.innerWidth <= 768);
@@ -18,6 +20,7 @@ export default function HeaderNav() {
   const navigateToHome = () => navigate("/");
   const navigateToChattingList = () => navigate("/chattinglist");
   const navigateToLogin = () => navigate("/login");
+  const navigateToMyPage = () => navigate("/mypage");
 
   return (
     <header className="bg-white shadow-md">
@@ -47,20 +50,14 @@ export default function HeaderNav() {
                 size="icon"
                 theme="black"
                 icon={<LogoIcon fillColor="#ffffff" />}
-                onClick={navigateToLogin}
+                onClick={user ? navigateToMyPage : navigateToLogin} // 조건부 이동
               />
             </>
           ) : (
             <>
-              <Button
-                size="icon"
-                theme="pink"
-                icon={<LogoIcon fillColor="#ffffff" />}
-                onClick={navigateToHome}
-              />
               <Button
                 size="lg"
-                theme="purple"
+                theme="pink"
                 icon={<LogoIcon fillColor="#ffffff" />}
                 onClick={navigateToHome}
               >
@@ -68,7 +65,7 @@ export default function HeaderNav() {
               </Button>
               <Button
                 size="lg"
-                theme="indigo"
+                theme="purple"
                 icon={<LogoIcon fillColor="#ffffff" />}
                 onClick={navigateToTimeTable}
               >
@@ -82,14 +79,25 @@ export default function HeaderNav() {
               >
                 번개채팅방
               </Button>
-              <Button
-                size="lg"
-                theme="black"
-                icon={<LogoIcon fillColor="#ffffff" />}
-                onClick={navigateToLogin}
-              >
-                로그인
-              </Button>
+              {user ? (
+                <Button
+                  size="lg"
+                  theme="black"
+                  icon={<LogoIcon fillColor="#ffffff" />}
+                  onClick={navigateToMyPage}
+                >
+                  마이페이지
+                </Button>
+              ) : (
+                <Button
+                  size="lg"
+                  theme="black"
+                  icon={<LogoIcon fillColor="#ffffff" />}
+                  onClick={navigateToLogin}
+                >
+                  로그인
+                </Button>
+              )}
             </>
           )}
         </div>
diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx
index f93bbc0..00aa883 100644
--- a/src/pages/LoginPage.jsx
+++ b/src/pages/LoginPage.jsx
@@ -1,7 +1,92 @@
-import React from "react";
+import React, { useEffect, useState } from "react";
+import GoogleLogo from "../components/icons/GoogleLogoIcon";
+import Button from "../components/Button";
+import { getLoginUrl } from "../api/auth";
+import useAuthStore from "../store/authStore";
 
 const LoginPage = () => {
-  return <></>;
+  const { user, fetchSession, logoutUser } = useAuthStore();
+  const [loading, setLoading] = useState(true);
+
+  // 페이지 로드 시 세션 확인
+  useEffect(() => {
+    const fetchSessionInfo = async () => {
+      setLoading(true);
+      try {
+        await fetchSession(); // 세션 정보 가져오기
+      } catch (error) {
+        console.error("Failed to fetch session info:", error);
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchSessionInfo();
+  }, [fetchSession]);
+
+  // Google 로그인 처리
+  const handleGoogleLogin = () => {
+    const loginUrl = getLoginUrl(); // 로그인 URL 가져오기
+    window.location.href = loginUrl; // 리다이렉트
+  };
+
+  // 로그아웃 처리
+  const handleLogout = async () => {
+    try {
+      setLoading(true);
+      await logoutUser();
+    } catch (error) {
+      console.error("Failed to logout:", error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  if (loading) {
+    return (
+      <div className="flex items-center justify-center min-h-screen">
+        <p>Loading...</p>
+      </div>
+    );
+  }
+
+  if (user) {
+    return (
+      <div className="flex items-center justify-center min-h-screen p-4 bg-gray-100">
+        <div className="flex flex-col items-center justify-center min-w-[260px] w-1/2 max-w-md p-8 space-y-4 bg-white rounded-lg shadow-lg">
+          <div>
+            <h2 className="text-center heading-2">환영합니다!</h2>
+            <h2 className="text-center heading-1">
+              <span className="text-primary-500">{user.name}</span>님
+            </h2>
+          </div>
+          <p className="text-center text-gray-700">
+            <span className="text-primary-500 title-1">번개모임</span>을 생성해
+            보세요!
+          </p>
+          <Button size="md" theme="black" onClick={handleLogout}>
+            로그아웃
+          </Button>
+        </div>
+      </div>
+    );
+  }
+
+  return (
+    <div className="flex items-center justify-center min-h-screen p-4 bg-gray-100">
+      <div className="flex flex-col items-center min-w-[260px] justify-center w-1/2 max-w-md p-8 space-y-6 bg-white rounded-lg shadow-lg">
+        <h2 className="text-2xl font-bold text-center text-gray-900">로그인</h2>
+        <Button
+          size="md"
+          theme="white"
+          icon={<GoogleLogo />}
+          onClick={handleGoogleLogin}
+        >
+          구글로 로그인
+        </Button>
+      </div>
+    </div>
+  );
 };
 
 export default LoginPage;
diff --git a/src/pages/SignUpPage.jsx b/src/pages/SignUpPage.jsx
deleted file mode 100644
index 4a4bbb5..0000000
--- a/src/pages/SignUpPage.jsx
+++ /dev/null
@@ -1,7 +0,0 @@
-import React from "react";
-
-const SignupPage = () => {
-  return <></>;
-};
-
-export default SignupPage;
diff --git a/src/store/authStore.js b/src/store/authStore.js
new file mode 100644
index 0000000..1d1223b
--- /dev/null
+++ b/src/store/authStore.js
@@ -0,0 +1,30 @@
+import { create } from "zustand";
+import { getSessionInfo, logout } from "../api/auth";
+
+const useAuthStore = create((set) => ({
+  user: null, // 사용자 정보
+  // user: { name: "윤석찬", email: "ysc0731@ajou.ac.kr" }, // 사용자 정보
+
+  // 세션 정보 가져오기
+  fetchSession: async () => {
+    try {
+      const userInfo = await getSessionInfo();
+      set({ user: userInfo });
+    } catch (error) {
+      console.error("Failed to fetch session info:", error);
+      set({ user: null });
+    }
+  },
+
+  // 로그아웃 처리
+  logoutUser: async () => {
+    try {
+      await logout();
+      set({ user: null });
+    } catch (error) {
+      console.error("Failed to logout:", error);
+    }
+  },
+}));
+
+export default useAuthStore;
-- 
GitLab


From a4a9e3e0a66490a16e287f7845f87db15f611741 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:31:41 +0900
Subject: [PATCH 3/6] =?UTF-8?q?hotfix:=20=EC=BA=98=EB=A6=B0=EB=8D=94=20?=
 =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=ED=83=80=EC=9E=84=EC=8A=AC?=
 =?UTF-8?q?=EB=A1=AF=20=EC=84=A0=ED=83=9D=20=EC=98=A4=EB=A5=98=20=ED=95=B4?=
 =?UTF-8?q?=EA=B2=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/pages/SchedulePage.jsx | 48 +++++++++++++++++++-------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx
index 5fa5f28..2f41569 100644
--- a/src/pages/SchedulePage.jsx
+++ b/src/pages/SchedulePage.jsx
@@ -74,34 +74,34 @@ const SchedulePage = () => {
     if (!isEditMode) return;
 
     // API
-    try {
-      const response = await fetchScheduleByTimeIndex(timeIdx);
-      if (response && response.data && response.data.schedule) {
-        setSelectedSchedule(response.data.schedule); // API로 가져온 스케줄 설정
-      } else {
-        console.error("No schedule found for time index:", timeIdx);
-      }
-    } catch (error) {
-      console.error("Failed to fetch schedule for time index:", timeIdx, error);
-    }
+    // try {
+    //   const response = await fetchScheduleByTimeIndex(timeIdx);
+    //   if (response && response.data && response.data.schedule) {
+    //     setSelectedSchedule(response.data.schedule); // API로 가져온 스케줄 설정
+    //   } else {
+    //     console.error("No schedule found for time index:", timeIdx);
+    //   }
+    // } catch (error) {
+    //   console.error("Failed to fetch schedule for time index:", timeIdx, error);
+    // }
 
     // 임시 코드
-    // const slotInSchedule = schedules.find((s) =>
-    //   s.time_indices.includes(timeIdx)
-    // );
+    const slotInSchedule = schedules.find((s) =>
+      s.time_indices.includes(timeIdx)
+    );
 
-    // if (slotInSchedule) {
-    //   if (selectedSlots.length === 0) {
-    //     setSelectedSchedule(slotInSchedule);
-    //   }
-    //   return;
-    // }
+    if (slotInSchedule) {
+      if (selectedSlots.length === 0) {
+        setSelectedSchedule(slotInSchedule);
+      }
+      return;
+    }
 
-    // if (selectedSlots.includes(timeIdx)) {
-    //   setSelectedSlots((prev) => prev.filter((idx) => idx !== timeIdx));
-    // } else {
-    //   setSelectedSlots((prev) => [...prev, timeIdx]);
-    // }
+    if (selectedSlots.includes(timeIdx)) {
+      setSelectedSlots((prev) => prev.filter((idx) => idx !== timeIdx));
+    } else {
+      setSelectedSlots((prev) => [...prev, timeIdx]);
+    }
   };
 
   const handleCancelSchedule = () => {
-- 
GitLab


From 443cc7ee623da58ad97c645368f552b05e8449e9 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:32:07 +0900
Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?=
 =?UTF-8?q?=EC=83=81=ED=83=9C=ED=99=95=EC=9D=B8=20UI=20=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/components/layout/HeaderLogoBar.jsx | 31 ++++++++++++---
 src/pages/HomePage.jsx                  | 27 ++++++++++++-
 src/pages/LoginPage.jsx                 | 50 +++++++++----------------
 src/store/authStore.js                  |  5 ++-
 4 files changed, 72 insertions(+), 41 deletions(-)

diff --git a/src/components/layout/HeaderLogoBar.jsx b/src/components/layout/HeaderLogoBar.jsx
index 4e08f2d..5772d41 100644
--- a/src/components/layout/HeaderLogoBar.jsx
+++ b/src/components/layout/HeaderLogoBar.jsx
@@ -1,23 +1,42 @@
-import React from "react";
+import React, { useState } from "react";
 import LogoIcon from "../icons/LogoIcon";
 import useAuthStore from "../../store/authStore";
+import Button from "../Button";
 
 const HeaderLogoBar = () => {
-  const { user } = useAuthStore(); // Zustand에서 user 상태 가져오기
+  const { user, logoutUser } = useAuthStore(); // Zustand에서 상태 및 메서드 가져오기
+  const [loading, setLoading] = useState(false); // 로딩 상태 관리
+
+  // 로그아웃 처리
+  const handleLogout = async () => {
+    try {
+      setLoading(true); // 로딩 상태 활성화
+      await logoutUser(); // 로그아웃 실행
+    } catch (error) {
+      console.error("Failed to logout:", error);
+    } finally {
+      setLoading(false); // 로딩 상태 비활성화
+    }
+  };
 
   return (
-    <div className="flex items-center justify-between w-full h-16 px-4 bg-white">
+    <div className="flex items-center justify-between w-full h-16 px-4 bg-white shadow-md">
       {/* 왼쪽: 로고와 앱 이름 */}
       <div className="flex items-center">
         <LogoIcon width={32} height={32} />
-        <span className="title-1">YANAWA</span>
+        <span className="ml-2 text-lg font-bold text-gray-900">YANAWA</span>
       </div>
 
-      {/* 오른쪽: 사용자 이름 */}
+      {/* 오른쪽: 사용자 정보 및 로그아웃 */}
       <div className="flex items-center">
-        <span className="text-gray-600 label-1">
+        <span className="mr-3 text-gray-600 label-1">
           {user ? `${user.name}` : "guest"} 님
         </span>
+        {user && (
+          <Button size="sm" onClick={handleLogout} disabled={loading}>
+            {loading ? "로그아웃 중..." : "로그아웃"}
+          </Button>
+        )}
       </div>
     </div>
   );
diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx
index 68fc933..01e46fe 100644
--- a/src/pages/HomePage.jsx
+++ b/src/pages/HomePage.jsx
@@ -1,9 +1,32 @@
-import React from "react";
+import React, { useEffect } from "react";
+import useAuthStore from "../store/authStore";
 
 const HomePage = () => {
+  const { user, fetchSession } = useAuthStore(); // Zustand 상태 및 메서드 가져오기
+
+  useEffect(() => {
+    const fetchUserSession = async () => {
+      try {
+        await fetchSession(); // 세션 정보 가져오기
+      } catch (error) {
+        console.error("Failed to fetch session:", error);
+      }
+    };
+
+    fetchUserSession();
+  }, [fetchSession]); // 페이지 마운트 시 실행
+
   return (
-    <div className="w-full h-screen flex items-center justify-center">
+    <div className="flex flex-col items-center justify-center w-full h-screen space-y-4">
       <h1 className="heading-1">야나와 홈페이지</h1>
+      {user ? (
+        <p className="text-lg text-gray-700">
+          안녕하세요,{" "}
+          <span className="font-bold text-primary-500">{user.name}</span> 님!
+        </p>
+      ) : (
+        <p className="text-lg text-gray-700">로그인이 필요합니다.</p>
+      )}
     </div>
   );
 };
diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx
index 00aa883..f2ad8ff 100644
--- a/src/pages/LoginPage.jsx
+++ b/src/pages/LoginPage.jsx
@@ -1,27 +1,15 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect } from "react";
 import GoogleLogo from "../components/icons/GoogleLogoIcon";
 import Button from "../components/Button";
 import { getLoginUrl } from "../api/auth";
 import useAuthStore from "../store/authStore";
 
 const LoginPage = () => {
-  const { user, fetchSession, logoutUser } = useAuthStore();
-  const [loading, setLoading] = useState(true);
+  const { user, fetchSession, logoutUser, loading } = useAuthStore(); // Zustand 상태 가져오기
 
   // 페이지 로드 시 세션 확인
   useEffect(() => {
-    const fetchSessionInfo = async () => {
-      setLoading(true);
-      try {
-        await fetchSession(); // 세션 정보 가져오기
-      } catch (error) {
-        console.error("Failed to fetch session info:", error);
-      } finally {
-        setLoading(false);
-      }
-    };
-
-    fetchSessionInfo();
+    fetchSession(); // 세션 정보 가져오기
   }, [fetchSession]);
 
   // Google 로그인 처리
@@ -32,37 +20,34 @@ const LoginPage = () => {
 
   // 로그아웃 처리
   const handleLogout = async () => {
-    try {
-      setLoading(true);
-      await logoutUser();
-    } catch (error) {
-      console.error("Failed to logout:", error);
-    } finally {
-      setLoading(false);
-    }
+    await logoutUser(); // 로그아웃 실행
   };
 
+  // 로딩 상태 처리
   if (loading) {
     return (
-      <div className="flex items-center justify-center min-h-screen">
-        <p>Loading...</p>
+      <div className="flex items-center justify-center min-h-screen bg-gray-100">
+        <p className="text-lg text-gray-600">로딩 중...</p>
       </div>
     );
   }
 
+  // 로그인된 상태
   if (user) {
     return (
       <div className="flex items-center justify-center min-h-screen p-4 bg-gray-100">
-        <div className="flex flex-col items-center justify-center min-w-[260px] w-1/2 max-w-md p-8 space-y-4 bg-white rounded-lg shadow-lg">
+        <div className="flex flex-col items-center min-w-[260px] w-1/2 max-w-md p-8 space-y-4 bg-white rounded-lg shadow-lg">
           <div>
-            <h2 className="text-center heading-2">환영합니다!</h2>
-            <h2 className="text-center heading-1">
-              <span className="text-primary-500">{user.name}</span>님
+            <h2 className="text-2xl font-semibold text-center text-gray-900">
+              환영합니다!
+            </h2>
+            <h2 className="text-xl font-bold text-center text-primary-500">
+              {user.name}님
             </h2>
           </div>
-          <p className="text-center text-gray-700">
-            <span className="text-primary-500 title-1">번개모임</span>을 생성해
-            보세요!
+          <p className="text-center text-gray-600">
+            <span className="font-bold text-primary-500">번개모임</span>을
+            생성하거나 참여해 보세요!
           </p>
           <Button size="md" theme="black" onClick={handleLogout}>
             로그아웃
@@ -72,6 +57,7 @@ const LoginPage = () => {
     );
   }
 
+  // 비로그인 상태
   return (
     <div className="flex items-center justify-center min-h-screen p-4 bg-gray-100">
       <div className="flex flex-col items-center min-w-[260px] justify-center w-1/2 max-w-md p-8 space-y-6 bg-white rounded-lg shadow-lg">
diff --git a/src/store/authStore.js b/src/store/authStore.js
index 1d1223b..05da3b6 100644
--- a/src/store/authStore.js
+++ b/src/store/authStore.js
@@ -3,16 +3,19 @@ import { getSessionInfo, logout } from "../api/auth";
 
 const useAuthStore = create((set) => ({
   user: null, // 사용자 정보
-  // user: { name: "윤석찬", email: "ysc0731@ajou.ac.kr" }, // 사용자 정보
+  loading: true, // 로딩 상태
 
   // 세션 정보 가져오기
   fetchSession: async () => {
+    set({ loading: true });
     try {
       const userInfo = await getSessionInfo();
       set({ user: userInfo });
     } catch (error) {
       console.error("Failed to fetch session info:", error);
       set({ user: null });
+    } finally {
+      set({ loading: false });
     }
   },
 
-- 
GitLab


From e6e575293c6a8fcfd730aabb14eb9c945939470e 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:34:30 +0900
Subject: [PATCH 5/6] =?UTF-8?q?docs:=20merge=20request=20template=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

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


From 3e7965be8806098281a30da8e4dbc1c1b7fd6b09 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:52:54 +0900
Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=EA=B0=9C=EB=B3=84=20=EC=8A=A4?=
 =?UTF-8?q?=EC=BC=80=EC=A5=B4=20=EC=A1=B0=ED=9A=8C=20api=20=EB=B9=84?=
 =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

diff --git a/src/pages/SchedulePage.jsx b/src/pages/SchedulePage.jsx
index 2f41569..6dcb5c7 100644
--- a/src/pages/SchedulePage.jsx
+++ b/src/pages/SchedulePage.jsx
@@ -3,7 +3,7 @@ import Label from "../components/Label";
 import {
   createSchedule,
   deleteSchedule,
-  fetchScheduleByTimeIndex,
+  // fetchScheduleByTimeIndex,
   fetchAllSchedules,
   updateSchedule,
 } from "../api/schedule";
-- 
GitLab