From 634bebca0070eed442e668a87bd5cc6dda38216c 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:14:16 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20user=20=EC=83=81=ED=83=9C=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20justand=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 31 ++++++++++++++++++++++++++++++- package.json | 3 ++- 2 files changed, 32 insertions(+), 2 deletions(-) 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", -- GitLab From 39016ad6a8d53acc95b7ac37acc4eea23d9da234 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:15:19 +0900 Subject: [PATCH 2/5] =?UTF-8?q?remove:=20=EC=9E=90=EC=B2=B4=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=82=AD=EC=A0=9C,=20=EC=86=8C=EC=85=9C?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=EC=9C=BC=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 2 -- src/pages/SignUpPage.jsx | 7 ------- 2 files changed, 9 deletions(-) delete mode 100644 src/pages/SignUpPage.jsx 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/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; -- GitLab From 64aa439bc7eeca7a40d038f2168ebc3ee5f06a0c 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:15:46 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=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=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth.js | 54 +++++++++++++++ src/components/icons/GoogleLogoIcon.jsx | 28 ++++++++ src/pages/LoginPage.jsx | 89 ++++++++++++++++++++++++- src/store/authStore.js | 29 ++++++++ 4 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 src/api/auth.js create mode 100644 src/components/icons/GoogleLogoIcon.jsx create mode 100644 src/store/authStore.js 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/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/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/store/authStore.js b/src/store/authStore.js new file mode 100644 index 0000000..2bf688c --- /dev/null +++ b/src/store/authStore.js @@ -0,0 +1,29 @@ +import { create } from "zustand"; +import { getSessionInfo, logout } from "../api/auth"; + +const useAuthStore = create((set) => ({ + user: null, // 사용자 정보 + + // 세션 정보 가져오기 + 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 628a44e123dac335135d38e65da0134b51791473 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:16:15 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=EB=B0=B1=EC=97=94=EB=93=9C=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A3=BC?= =?UTF-8?q?=EC=86=8C=20=EC=88=98=EC=A0=95=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/schedule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 () => { -- GitLab From 3f31ecafeb5addd0be340448ca0344c120ad46a5 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:29:49 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=97=A4?= =?UTF-8?q?=EB=94=A9=EB=B0=94=20=EB=B3=80=ED=98=95=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/layout/HeaderLogoBar.jsx | 14 ++++++++- src/components/layout/HeaderNav.jsx | 42 +++++++++++++++---------- src/store/authStore.js | 1 + 3 files changed, 39 insertions(+), 18 deletions(-) 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/store/authStore.js b/src/store/authStore.js index 2bf688c..1d1223b 100644 --- a/src/store/authStore.js +++ b/src/store/authStore.js @@ -3,6 +3,7 @@ import { getSessionInfo, logout } from "../api/auth"; const useAuthStore = create((set) => ({ user: null, // 사용자 정보 + // user: { name: "윤석찬", email: "ysc0731@ajou.ac.kr" }, // 사용자 정보 // 세션 정보 가져오기 fetchSession: async () => { -- GitLab