Skip to content
Snippets Groups Projects
Commit 6a641de7 authored by 석찬 윤's avatar 석찬 윤
Browse files

[#10] 로그인 페이지 개발

parent 906a7532
No related branches found
No related tags found
4 merge requests!13[#7] 채팅방 목록, 채팅방 상세 페이지 디자인, 컴포넌트 구현,!11[Hotfix] 스케줄 타임슬롯 선택 오류 해결 및 로그인 시 UI 개선,!9[#10] 로그인 페이지 배포,!8[#10] 로그인 페이지 개발
......@@ -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
}
}
}
}
}
......@@ -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",
......
......@@ -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 />
......
// 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;
}
};
// 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 () => {
......
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>
);
}
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>
);
};
......
......@@ -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,6 +79,16 @@ export default function HeaderNav() {
>
번개채팅방
</Button>
{user ? (
<Button
size="lg"
theme="black"
icon={<LogoIcon fillColor="#ffffff" />}
onClick={navigateToMyPage}
>
마이페이지
</Button>
) : (
<Button
size="lg"
theme="black"
......@@ -90,6 +97,7 @@ export default function HeaderNav() {
>
로그인
</Button>
)}
</>
)}
</div>
......
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;
import React from "react";
const SignupPage = () => {
return <></>;
};
export default SignupPage;
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;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment