diff --git a/src/apis/hooks/pots/useGetPots.ts b/src/apis/hooks/pots/useGetPots.ts index 26a45f0a..b1003ecc 100644 --- a/src/apis/hooks/pots/useGetPots.ts +++ b/src/apis/hooks/pots/useGetPots.ts @@ -5,12 +5,18 @@ import { GetPotsParams } from "apis/types/pot"; const useGetPots = ({ page, size, - recruitmentRole, + recruitmentRoles, onlyMine, }: GetPotsParams) => { return useQuery({ - queryKey: ["pots", page, recruitmentRole, onlyMine], - queryFn: () => GetPots({ page, size, recruitmentRole, onlyMine }), + queryKey: ["pots", page, size, recruitmentRoles ?? null, onlyMine], + queryFn: () => + GetPots({ + page, + size, + recruitmentRoles: recruitmentRoles ?? null, + onlyMine, + }), select: (data) => data.result, }); }; diff --git a/src/apis/hooks/users/useGetSignIn.ts b/src/apis/hooks/users/useGetSignIn.ts index 2b3a5d7a..824fc17c 100644 --- a/src/apis/hooks/users/useGetSignIn.ts +++ b/src/apis/hooks/users/useGetSignIn.ts @@ -1,13 +1,24 @@ import routes from "@constants/routes"; import { useMutation } from "@tanstack/react-query"; -import { getKakaoLogIn } from "apis/userAPI"; +import { getGoogleLogIn, getKakaoLogIn, getNaverLogIn } from "apis/userAPI"; import { useNavigate } from "react-router-dom"; -const useGetSignIn = () => { +const useGetSignIn = (signInType: string | undefined) => { const navigate = useNavigate(); return useMutation({ - mutationFn: (code: string) => getKakaoLogIn(code), + mutationFn: (code: string) => { + switch (signInType) { + case "google": + return getGoogleLogIn(code); + case "kakao": + return getKakaoLogIn(code); + case "naver": + return getNaverLogIn(code); + default: + throw new Error(`Invalid login type: ${signInType}`); + } + }, onSuccess: (data) => { if (data.result) { const { accessToken, refreshToken } = data.result.tokenServiceResponse; diff --git a/src/apis/potAPI.ts b/src/apis/potAPI.ts index 7b8d8315..1d18d85c 100644 --- a/src/apis/potAPI.ts +++ b/src/apis/potAPI.ts @@ -32,13 +32,13 @@ export const PostPot = async (postPotParams: PostPotParams) => { export const GetPots = async ({ page, size, - recruitmentRole, + recruitmentRoles, onlyMine, }: GetPotsParams) => { return authApiGet("pots", { page, size, - recruitmentRole, + recruitmentRoles, onlyMine, }); }; diff --git a/src/apis/types/pot.ts b/src/apis/types/pot.ts index 556bf5fd..34671113 100644 --- a/src/apis/types/pot.ts +++ b/src/apis/types/pot.ts @@ -40,7 +40,7 @@ export interface RecruitmentDetailResponse { export interface GetPotsParams { page: number; size: number; - recruitmentRole: string | null; + recruitmentRoles: string | null; onlyMine: boolean; } diff --git a/src/apis/userAPI.ts b/src/apis/userAPI.ts index 480a3f99..100e370c 100644 --- a/src/apis/userAPI.ts +++ b/src/apis/userAPI.ts @@ -28,10 +28,18 @@ import { PostPotResponse, } from "./types/pot"; +export const getGoogleLogIn = async (code: string) => { + return apiGet("/users/oauth/google", { code }); +}; + export const getKakaoLogIn = async (code: string) => { return apiGet("/users/oauth/kakao", { code }); }; +export const getNaverLogIn = async (code: string) => { + return apiGet("/users/oauth/naver", { code }); +}; + export const GetMyUser = async () => { return authApiGet("/users"); }; @@ -53,6 +61,15 @@ export const postNickname = async (nickname: string) => { }); }; +// export const GetMyPage = async ({ dataType }: GetMyPageParams) => { +// if (dataType === "feed") { +// return authApiGet("/users/mypages"); +// } else if (dataType === "pot") { +// return authApiGet("/users/mypages", { dataType }); +// } else { +// return authApiGet("/users/description"); +// } +// }; export const getMyPageFeeds = async ({ nextCursor, size, diff --git a/src/components/cards/CtaCard/CtaCard.tsx b/src/components/cards/CtaCard/CtaCard.tsx index 9197349a..fa79615e 100644 --- a/src/components/cards/CtaCard/CtaCard.tsx +++ b/src/components/cards/CtaCard/CtaCard.tsx @@ -7,6 +7,8 @@ import { } from "./CtaCard.style"; import { useNavigate } from "react-router-dom"; import routes from "@constants/routes"; +import { useState } from "react"; +import LoginModal from "@components/commons/Modal/LoginModal/LoginModal"; import { SproutImage } from "@assets/images"; interface CtaCardProps { @@ -15,10 +17,13 @@ interface CtaCardProps { const CtaCard: React.FC = ({ type }: CtaCardProps) => { const navigate = useNavigate(); - const accessToken = localStorage.getItem("accessToken"); + const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); const handleClick = () => { - if (accessToken) { + const token = localStorage.getItem("accessToken"); + if (token === null) { + setIsLoginModalOpen(true); + } else { if (type === "feed") { navigate(routes.writePost); } else if (type === "pot") { @@ -29,19 +34,24 @@ const CtaCard: React.FC = ({ type }: CtaCardProps) => { }; return ( -
- -

- {type == "feed" - ? "오늘 작업하다가 무슨 일이 있었냐면..." - : "꿈을 현실로 옮길 시간이에요. 팟을 만들고 팀원을 모집해 볼까요?"} -

-
- + <> +
+ +

+ {type == "feed" + ? "오늘 작업하다가 무슨 일이 있었냐면..." + : "꿈을 현실로 옮길 시간이에요. 팟을 만들고 팀원을 모집해 볼까요?"} +

+
+ +
-
+ {isLoginModalOpen && ( + setIsLoginModalOpen(false)} /> + )} + ); }; export default CtaCard; diff --git a/src/components/commons/Modal/LoginModal/LoginModal.tsx b/src/components/commons/Modal/LoginModal/LoginModal.tsx index 277f0a84..07389b5c 100644 --- a/src/components/commons/Modal/LoginModal/LoginModal.tsx +++ b/src/components/commons/Modal/LoginModal/LoginModal.tsx @@ -20,13 +20,21 @@ interface LoginModalProps { } const LoginModal: React.FC = ({ onCancel }) => { - const googleLoginLink = ""; + const googleLoginLink = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${ + import.meta.env.VITE_REST_GOOGLE_API_KEY + }&redirect_uri=${ + import.meta.env.VITE_GOOGLE_REDIRECT_URI + }&response_type=code&scope=email`; + const kakaoLoginLink = `https://kauth.kakao.com/oauth/authorize?client_id=${ import.meta.env.VITE_REST_API_KEY }&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}&response_type=code &scope=account_email &prompt=login`; - const naverLoginLink = ""; + + const naverLoginLink = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${ + import.meta.env.VITE_REST_NAVER_API_KEY + }&redirect_uri=${import.meta.env.VITE_NAVER_REDIRECT_URI}`; const handleLogin = (link: string) => { window.location.href = link; diff --git a/src/components/layouts/SideBar/SideBar.tsx b/src/components/layouts/SideBar/SideBar.tsx index 041e93bd..76d22884 100644 --- a/src/components/layouts/SideBar/SideBar.tsx +++ b/src/components/layouts/SideBar/SideBar.tsx @@ -1,6 +1,15 @@ import React, { useState, useEffect } from "react"; import { NavLink } from "react-router-dom"; -import { MyPotIcon, HomeIcon, HomeFilledIcon, PotIcon, LogoFilledIcon, ChatIcon, MyPotFilledIcon, ChatFilledIcon } from "@assets/svgs"; +import { + MyPotIcon, + HomeIcon, + HomeFilledIcon, + PotIcon, + LogoFilledIcon, + ChatIcon, + MyPotFilledIcon, + ChatFilledIcon, +} from "@assets/svgs"; import { container, iconStyle, @@ -11,37 +20,38 @@ import { menuItemStyle, } from "./SideBar.style"; import routes from "@constants/routes"; - - +import LoginModal from "@components/commons/Modal/LoginModal/LoginModal"; const menuItems = [ { to: routes.home, icon: , activeIcon: , - label: '홈' + label: "홈", }, { to: routes.pot.base, icon: , activeIcon: , - label: '모든팟', + label: "모든팟", }, { to: routes.myPot.base, icon: , activeIcon: , - label: '나의팟', - }, { + label: "나의팟", + }, + { to: routes.chat, icon: , activeIcon: , - label: '채팅', + label: "채팅", }, ]; const SideBar: React.FC = () => { const [top, setTop] = useState(0); + const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); useEffect(() => { const initialTop = window.innerHeight / 2 + window.scrollY; @@ -67,31 +77,42 @@ const SideBar: React.FC = () => { }, []); return ( -
-
- {menuItems.map(({ to, icon, activeIcon, label }, index) => { - const accessToken = localStorage.getItem("accessToken"); - const isPrivateRoute = to === routes.myPot.base || to === routes.chat; - const link = `https://kauth.kakao.com/oauth/authorize?client_id=${import.meta.env.VITE_REST_API_KEY}&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}&response_type=code&scope=account_email&prompt=login`; + <> +
+
+ {menuItems.map(({ to, icon, activeIcon, label }, index) => { + const accessToken = localStorage.getItem("accessToken"); + const isPrivateRoute = + to === routes.myPot.base || to === routes.chat; - return ( - getNavLinkStyle(isActive)} - css={menuItemStyle} - > - {({ isActive }) => ( - <> - {isActive ? activeIcon : icon} -

{label}

- - )} -
- ); - })} + return ( + getNavLinkStyle(isActive)} + css={menuItemStyle} + onClick={(e) => { + if (!accessToken && isPrivateRoute) { + e.preventDefault(); + setIsLoginModalOpen(true); + } + }} + > + {({ isActive }) => ( + <> + {isActive ? activeIcon : icon} +

{label}

+ + )} +
+ ); + })} +
-
+ {isLoginModalOpen && ( + setIsLoginModalOpen(false)} /> + )} + ); }; diff --git a/src/pages/Callback/Callback.tsx b/src/pages/Callback/Callback.tsx index 3363035b..f32d5c8f 100644 --- a/src/pages/Callback/Callback.tsx +++ b/src/pages/Callback/Callback.tsx @@ -2,11 +2,15 @@ import { useEffect, useRef } from "react"; import useGetSignIn from "apis/hooks/users/useGetSignIn"; import { LoadingSpinnerIcon } from "@assets/svgs"; import { container, iconStyle } from "./Callback.style"; +import { useNavigate, useParams } from "react-router-dom"; +import routes from "@constants/routes"; const Callback: React.FC = () => { + const navigate = useNavigate(); + const { loginType } = useParams(); const isCodeProcessed = useRef(false); const code = new URL(window.location.href).searchParams.get("code"); - const { mutate } = useGetSignIn(); + const { mutate } = useGetSignIn(loginType); useEffect(() => { if (code && !isCodeProcessed.current) { @@ -15,6 +19,13 @@ const Callback: React.FC = () => { } }, [code, mutate]); + useEffect(() => { + if (!loginType || !["google", "kakao", "naver"].includes(loginType)) { + console.error("Invalid loginType:", loginType); + navigate(routes.home); + } + }, [loginType]); + return (
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index b1abf86c..7d9c15c2 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -19,20 +19,19 @@ import "swiper"; import { useNavigate } from "react-router-dom"; import routes from "@constants/routes"; import { Feed, PopularPots } from "./components"; +import { useState } from "react"; +import LoginModal from "@components/commons/Modal/LoginModal/LoginModal"; const Home: React.FC = () => { const navigate = useNavigate(); - const link = `https://kauth.kakao.com/oauth/authorize?client_id=${import.meta.env.VITE_REST_API_KEY - }&redirect_uri=${import.meta.env.VITE_REDIRECT_URI}&response_type=code -&scope=account_email -&prompt=login`; + const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); const handleClick = () => { const token = localStorage.getItem("accessToken"); if (token) { navigate(routes.createPot); } else { - window.location.href = link; + setIsLoginModalOpen(true); } }; @@ -68,6 +67,9 @@ const Home: React.FC = () => {
+ {isLoginModalOpen && ( + setIsLoginModalOpen(false)} /> + )} ); }; diff --git a/src/pages/Home/components/Feed/Feed.tsx b/src/pages/Home/components/Feed/Feed.tsx index 5b2399f4..e22145c9 100644 --- a/src/pages/Home/components/Feed/Feed.tsx +++ b/src/pages/Home/components/Feed/Feed.tsx @@ -22,6 +22,7 @@ import routes from "@constants/routes"; import useGetMyProfile from "apis/hooks/users/useGetMyProfile"; import { useNavigate } from "react-router-dom"; +import LoginModal from "@components/commons/Modal/LoginModal/LoginModal"; const options = [ { label: "최신 순", key: "new" }, @@ -46,6 +47,8 @@ const Feed = () => { const navigate = useNavigate(); const [selectedCategory, setSelectedCategory] = useState("전체보기"); const [sort, setSort] = useState("new"); + const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); + const { data: user } = useGetMyProfile(!!localStorage.getItem("accessToken")); const handleCategoryClick = (partName: string) => { @@ -65,10 +68,7 @@ const Feed = () => { if (token) { navigate(routes.writePost); } else { - const link = `https://kauth.kakao.com/oauth/authorize?client_id=${import.meta.env.VITE_REST_API_KEY - }&redirect_uri=${import.meta.env.VITE_REDIRECT_URI - }&response_type=code&scope=account_email&prompt=login`; - window.location.href = link; + setIsLoginModalOpen(true); } }; @@ -172,6 +172,9 @@ const Feed = () => { )} + {isLoginModalOpen && ( + setIsLoginModalOpen(false)} /> + )} ); }; diff --git a/src/pages/Pots/PotMain.tsx b/src/pages/Pots/PotMain.tsx index 8e7268b7..13b99624 100644 --- a/src/pages/Pots/PotMain.tsx +++ b/src/pages/Pots/PotMain.tsx @@ -18,6 +18,12 @@ const PotMain: React.FC = () => { end={tab.path === routes.pot.base} css={tabsTextStyle} className={({ isActive }) => (isActive ? "active" : "")} + onClick={(e) => { + const accessToken = localStorage.getItem("accessToken"); + if (!accessToken && tab.label === "지원한 팟") { + e.preventDefault(); + } + }} > {tab.label} diff --git a/src/pages/Pots/pages/AllPot/AllPotPage.tsx b/src/pages/Pots/pages/AllPot/AllPotPage.tsx index 58e0cd1c..d55047d9 100644 --- a/src/pages/Pots/pages/AllPot/AllPotPage.tsx +++ b/src/pages/Pots/pages/AllPot/AllPotPage.tsx @@ -25,14 +25,17 @@ const AllPotPage: React.FC = () => { const { data } = useGetPots({ page: currentPage, size: 9, - recruitmentRole: partMap[selectedCategory], + recruitmentRoles: partMap[selectedCategory] ?? null, onlyMine: isMyPot, }); const handleClick = (partName: string) => { - setCurrentPage(1); - setSelectedCategory(partName); - setIsMyPot(partName === "내가 만든 팟"); + const accessToken = localStorage.getItem("accessToken"); + if (accessToken || partName !== "내가 만든 팟") { + setCurrentPage(1); + setSelectedCategory(partName); + setIsMyPot(partName === "내가 만든 팟"); + } }; const handlePageChange = ( diff --git a/src/pages/SignUp/components/CompleteModal/CompleteModal.tsx b/src/pages/SignUp/components/CompleteModal/CompleteModal.tsx index 2f52b7b2..594c2ad9 100644 --- a/src/pages/SignUp/components/CompleteModal/CompleteModal.tsx +++ b/src/pages/SignUp/components/CompleteModal/CompleteModal.tsx @@ -29,7 +29,7 @@ function CompleteModal({ onModalCancel }: CompleteModalProps) { title={`가입이 완료되었어요! 완성된 나의 프로필이에요.`} buttonText="메인으로" onButtonClick={handleConfirm} - onCancel={onModalCancel} + onCancel={handleConfirm} customContainerStyle={modalStyle} >
diff --git a/src/routes/router.tsx b/src/routes/router.tsx index 02ce91ec..fb3944a3 100644 --- a/src/routes/router.tsx +++ b/src/routes/router.tsx @@ -94,7 +94,7 @@ const router = createBrowserRouter([ ], }, { - path: routes.callback, + path: `${routes.callback}/:loginType`, element: , }, {