diff --git a/package-lock.json b/package-lock.json index d3a3f93..601aa36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "react-dom": "^19.2.0", "react-router-dom": "^7.11.0", "react-spinners": "^0.17.0", + "react-toastify": "^11.0.5", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.18", "vite-plugin-svgr": "^4.5.0", @@ -4313,6 +4314,19 @@ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-toastify": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz", + "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", diff --git a/package.json b/package.json index 2adcaa1..ae23231 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react-dom": "^19.2.0", "react-router-dom": "^7.11.0", "react-spinners": "^0.17.0", + "react-toastify": "^11.0.5", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.18", "vite-plugin-svgr": "^4.5.0", diff --git a/src/App.tsx b/src/App.tsx index 500e52d..200b857 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,23 @@ import { RouterProvider } from "react-router-dom"; import router from "./routes"; +import { Slide, ToastContainer } from "react-toastify"; function App() { return (
-
+
+ + "flex items-center justify-between p-4 px-8 rounded-lg bg-white text-black shadow-md" + } + progressClassName={"bg-red-500"} + hideProgressBar={true} + transition={Slide} + />
); } diff --git a/src/assets/icons/alert2.svg b/src/assets/icons/alert2.svg new file mode 100644 index 0000000..a829c1c --- /dev/null +++ b/src/assets/icons/alert2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/layout/OnboardingLayout.tsx b/src/layout/OnboardingLayout.tsx index 3e0e515..211d22e 100644 --- a/src/layout/OnboardingLayout.tsx +++ b/src/layout/OnboardingLayout.tsx @@ -5,7 +5,7 @@ import clsx from "clsx"; export const OnboardingLayout = () => { const { pathname } = useLocation(); return ( -
+
diff --git a/src/layout/SystemLayout.tsx b/src/layout/SystemLayout.tsx index 7c51d0a..42fe3b6 100644 --- a/src/layout/SystemLayout.tsx +++ b/src/layout/SystemLayout.tsx @@ -3,9 +3,13 @@ import { SystemHeader } from "../shared/SystemHeader"; export const SystemLayout = () => { return ( -
- - -
+ <> +
+ +
+
+ +
+ ); }; diff --git a/src/lib/my.ts b/src/lib/my.ts index d4ebef4..80ff672 100644 --- a/src/lib/my.ts +++ b/src/lib/my.ts @@ -2,6 +2,7 @@ import { useMutation, + useQuery, useQueryClient, useSuspenseQuery, } from "@tanstack/react-query"; @@ -20,11 +21,12 @@ export const getMyProfile = async () => { return data; }; -export const useGetMyProfile = () => { - return useSuspenseQuery({ +export const useGetMyProfile = (enabled?: boolean) => { + return useQuery({ queryKey: ["my", "profile"], queryFn: getMyProfile, select: res => res.data, + enabled, }); }; diff --git a/src/lib/recommendation.ts b/src/lib/recommendation.ts index d0aa4bc..b900bbe 100644 --- a/src/lib/recommendation.ts +++ b/src/lib/recommendation.ts @@ -1,8 +1,4 @@ -import { - useMutation, - useQueryClient, - useSuspenseQuery, -} from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import api from "./api"; //추천 게시글 조회 @@ -11,11 +7,12 @@ export const getRecommendPostList = async () => { return data; }; -export const useGetRecommendPostList = () => { - return useSuspenseQuery({ +export const useGetRecommendPostList = (isLogin: boolean) => { + return useQuery({ queryKey: ["posts", "my", "recommend"], queryFn: getRecommendPostList, select: res => res.data, + enabled: isLogin, }); }; diff --git a/src/pages/Login/KakaoLogin.tsx b/src/pages/Login/KakaoLogin.tsx index ae33492..7fe64c3 100644 --- a/src/pages/Login/KakaoLogin.tsx +++ b/src/pages/Login/KakaoLogin.tsx @@ -2,6 +2,8 @@ import { useEffect } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import useUserStore from "../../store/useUserStore"; import { useOnboardingStore } from "../../store/useOnboardingStore"; +import { toast } from "react-toastify"; +import Alert from "@/assets/icons/alert2.svg"; export const KakaoLogin = () => { const [searchParams] = useSearchParams(); @@ -13,10 +15,14 @@ export const KakaoLogin = () => { const token = searchParams.get("token"); const isMember = searchParams.get("registered"); const email = searchParams.get("email") ?? undefined; - console.log(email); + // console.log(email); setTemp({ email }); setUser({ accessToken: token, isNewMember: isMember }); if (!token) { + toast.info("로그인에 실패했어요. 다시 로그인해 주세요.", { + icon: login으로 이동, + }); + navigate("/login"); } if (isMember === "false") { diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 6a8e0cf..f08e91e 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -1,11 +1,9 @@ import { TabSelectList } from "./components/TabSelectList"; import { CompanyFilterList } from "./components/CompanyFilterList"; -import { InterestFilterList } from "./components/InterestFilterList"; import { PostCardList } from "./components/PostCardList"; import { Suspense, useState } from "react"; import { useCompanyStore } from "../../store/uesCompanyStore"; import { useGetCompany } from "../../lib/company"; -import { useGetMyInterest } from "../../lib/my"; import { usePostRecommendPostList } from "../../lib/recommendation"; import { TAB_MAP } from "../../constants/tab"; import { Loading } from "../../shared/Loading"; @@ -13,13 +11,16 @@ import { useNavigate, useSearchParams } from "react-router-dom"; import { useDebounce } from "../../hooks/useDebouce"; import { SearchPostList } from "./components/SearchPostList"; import useUserStore from "../../store/useUserStore"; +import { toast } from "react-toastify"; +import Alert from "@/assets/icons/alert2.svg"; +import { SkeletonList } from "../../shared/SkeletonList"; +import { InterestPage } from "./components/InterestPage"; export const HomePage = () => { const [selectedTab, setSelectedTab] = useState(0); const [modal, setModal] = useState(false); const { companies, toggleCompany } = useCompanyStore(); const { data: companyData } = useGetCompany(); - const { data: myInterest } = useGetMyInterest(); const { mutate: postRecommendList, isPending: isRefreshing } = usePostRecommendPostList(); @@ -27,13 +28,16 @@ export const HomePage = () => { const [searchParams] = useSearchParams(); const searchQuery = searchParams.get("search"); - console.log(searchQuery); + // console.log(searchQuery); const debouncedInput = useDebounce(searchQuery, 200); const { user } = useUserStore(); const isLogin = !!user?.accessToken; const navigate = useNavigate(); const handleTabChange = (tab: number) => { if (tab === 1 && !isLogin) { + toast.info("로그인이 필요한 서비스입니다.", { + icon: login으로 이동, + }); navigate("/login"); return; } @@ -42,6 +46,7 @@ export const HomePage = () => { return (
setModal(false)}> + {/* */} {debouncedInput && debouncedInput.trim() !== "" ? ( }> @@ -69,21 +74,15 @@ export const HomePage = () => { )} {/* 나와맞는 게시글 */} - {selectedTab === 1 && ( - <> - }> + - {isRefreshing ? ( - - ) : ( - }> - - - )} - + )} + {(selectedTab === 2 || selectedTab === 3) && ( )} diff --git a/src/pages/home/components/CompaniesModal.tsx b/src/pages/home/components/CompaniesModal.tsx index 622912d..41fb055 100644 --- a/src/pages/home/components/CompaniesModal.tsx +++ b/src/pages/home/components/CompaniesModal.tsx @@ -20,7 +20,7 @@ export const CompaniesModal = ({ companyData }: CompaniesModalProps) => { } }, [companies]); - console.log(companies); + // console.log(companies); return (
void; + isRefreshing: boolean; +} + +export const InterestPage = ({ + onRefresh, + isRefreshing, +}: InterestPageProps) => { + const { data: myInterest } = useGetMyInterest(); + + return ( + <> + + {isRefreshing ? ( + // 반복되는 Skeleton UI는 별도 컴포넌트로 빼면 깔끔합니다. + ) : ( + + )} + + ); +}; diff --git a/src/pages/home/components/PostCardList.tsx b/src/pages/home/components/PostCardList.tsx index 64200bc..1628200 100644 --- a/src/pages/home/components/PostCardList.tsx +++ b/src/pages/home/components/PostCardList.tsx @@ -6,6 +6,7 @@ import { useGetRecommendPostList } from "../../../lib/recommendation"; import type { CardItemProps, PostResponseDto } from "../../../types/post"; import { CardItem } from "../../../shared/CardItem"; import { Loading } from "../../../shared/Loading"; +import useUserStore from "../../../store/useUserStore"; interface PostCardListProps { selectedTab: number; @@ -18,7 +19,9 @@ export const PostCardList = ({ selectedTab }: PostCardListProps) => { const companyQuery = useInfiniteCompaniesPosts({ companies }); const recentQuery = useInfinitePosts({ sortBy: "LATEST" }); const popularQuery = useInfinitePosts({ sortBy: "POPULAR" }); - const { data: recommendData } = useGetRecommendPostList(); + const { user } = useUserStore(); + const isLogin = !!user?.accessToken; + const { data: recommendData } = useGetRecommendPostList(isLogin); const activeQuery = [companyQuery, null, recentQuery, popularQuery][ selectedTab @@ -45,7 +48,7 @@ export const PostCardList = ({ selectedTab }: PostCardListProps) => { (page: PostResponseDto) => page.data.posts, ) ?? []); - console.log(posts); + // console.log(posts); return ( <> diff --git a/src/pages/home/components/SearchPostList.tsx b/src/pages/home/components/SearchPostList.tsx index 78922d9..f201dbd 100644 --- a/src/pages/home/components/SearchPostList.tsx +++ b/src/pages/home/components/SearchPostList.tsx @@ -24,7 +24,7 @@ export const SearchPostList = ({ query }: SearchPostListProps) => { return
검색 결과가 없습니다.
; } - console.log(searchData); + // console.log(searchData); return (
    diff --git a/src/pages/home/components/TabSelectList.tsx b/src/pages/home/components/TabSelectList.tsx index cb99a07..f15f0a4 100644 --- a/src/pages/home/components/TabSelectList.tsx +++ b/src/pages/home/components/TabSelectList.tsx @@ -15,7 +15,7 @@ export const TabSelectList = ({ tagList, }: TabSelectList) => { return ( -
    +
    {tagList.map((item, idx) => { return ( { const posts = data?.flatMap(page => page.data.bookmarks) ?? []; - console.log(posts); + // console.log(posts); return (
    diff --git a/src/pages/mypage/SettingPage.tsx b/src/pages/mypage/SettingPage.tsx index ca02a21..c6de055 100644 --- a/src/pages/mypage/SettingPage.tsx +++ b/src/pages/mypage/SettingPage.tsx @@ -55,7 +55,7 @@ export const SettingPage = () => { }, ]; const { data: user } = useGetMyProfile(); - console.log(user); + // console.log(user); return (
    diff --git a/src/pages/mypage/components/ProfileEditHeader.tsx b/src/pages/mypage/components/ProfileEditHeader.tsx index cc2373b..416e85a 100644 --- a/src/pages/mypage/components/ProfileEditHeader.tsx +++ b/src/pages/mypage/components/ProfileEditHeader.tsx @@ -22,7 +22,7 @@ export const ProfileEditHeader = ({ const [name, setName] = useState(nickName); const [introduce, setIntroduce] = useState(description); - console.log(name, introduce); + // console.log(name, introduce); return (
    diff --git a/src/pages/onboarding/Onboarding.tsx b/src/pages/onboarding/Onboarding.tsx index b507077..3be00c2 100644 --- a/src/pages/onboarding/Onboarding.tsx +++ b/src/pages/onboarding/Onboarding.tsx @@ -21,8 +21,6 @@ export const Onboarding = () => { }; const isNicknameValid = nickname.length >= 2; const BtnAble = !isNicknameValid || !check; - console.log(BtnAble, "버튼"); - console.log(email); return (
    diff --git a/src/pages/onboarding/OnboardingTag.tsx b/src/pages/onboarding/OnboardingTag.tsx index f0f470c..c02f04c 100644 --- a/src/pages/onboarding/OnboardingTag.tsx +++ b/src/pages/onboarding/OnboardingTag.tsx @@ -55,7 +55,7 @@ export const OnboardingTag = () => { }); }; - console.log(tag); + // console.log(tag); return (
    diff --git a/src/shared/CardItem.tsx b/src/shared/CardItem.tsx index 698ee15..6df2340 100644 --- a/src/shared/CardItem.tsx +++ b/src/shared/CardItem.tsx @@ -9,6 +9,8 @@ import { } from "../lib/activity"; import useUserStore from "../store/useUserStore"; import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import Alert from "@/assets/icons/alert2.svg"; interface CardItemProps { id?: number; @@ -54,6 +56,9 @@ export const CardItem = forwardRef( if (!id) return; if (!isLogin) { + toast.info(`로그인이 필요한 서비스입니다.`, { + icon: login으로 이동, + }); navigate("/login"); return; } @@ -86,7 +91,10 @@ export const CardItem = forwardRef( } }; return ( -
  • +
  • {company} { className={cn("flex gap-2 items-center pb-23 w-full ", className)} > 로고 navigate("/")} diff --git a/src/shared/SkeletonCard.tsx b/src/shared/SkeletonCard.tsx new file mode 100644 index 0000000..6f2ccc7 --- /dev/null +++ b/src/shared/SkeletonCard.tsx @@ -0,0 +1,25 @@ +export const SkeletonCard = () => { + return ( +
  • +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
  • + ); +}; diff --git a/src/shared/SkeletonList.tsx b/src/shared/SkeletonList.tsx new file mode 100644 index 0000000..bc303ee --- /dev/null +++ b/src/shared/SkeletonList.tsx @@ -0,0 +1,11 @@ +import { SkeletonCard } from "./SkeletonCard"; + +export const SkeletonList = () => { + return ( +
      + {Array.from({ length: 16 }).map((_, i) => ( + + ))} +
    + ); +}; diff --git a/src/shared/SystemHeader.tsx b/src/shared/SystemHeader.tsx index 7234069..e9a8757 100644 --- a/src/shared/SystemHeader.tsx +++ b/src/shared/SystemHeader.tsx @@ -9,6 +9,8 @@ import { MYPAGE_NAV } from "../constants/mypage"; import useUserStore from "../store/useUserStore"; import { postLogout } from "../lib/auth"; import { useGetMyProfile } from "../lib/my"; +import { toast } from "react-toastify"; +import Alert from "@/assets/icons/alert2.svg"; export const SystemHeader = () => { const navigate = useNavigate(); @@ -16,8 +18,9 @@ export const SystemHeader = () => { const modalRef = useRef(null); const { user, logout } = useUserStore(); - const { data } = useGetMyProfile(); - console.log(data); + const isLogin = !!user?.accessToken; + + const { data } = useGetMyProfile(isLogin); const handleLogout = async () => { try { @@ -31,8 +34,6 @@ export const SystemHeader = () => { } }; - const isLogin = !!user?.accessToken; - const handleNavClick = (item: { name: string; nav?: string }) => { if (item.name === "로그아웃") { handleLogout(); @@ -61,9 +62,7 @@ export const SystemHeader = () => { }, [input, navigate]); return ( -
    +
    { )} mypage { - if (!isLogin) return navigate("/login"); + if (!isLogin) { + toast.info(`로그인이 sss필요한 서비스입니다.`, { + icon: login으로 이동, + }); + navigate("/login"); + } + setUserModal(prev => !prev); }} /> diff --git a/src/shared/button/TabBtn.tsx b/src/shared/button/TabBtn.tsx index e7ca67d..f01457f 100644 --- a/src/shared/button/TabBtn.tsx +++ b/src/shared/button/TabBtn.tsx @@ -6,12 +6,15 @@ interface TabBtnProps { onClick: () => void; } -export const TabBtn = ({ label = "탭", selected, onClick }: TabBtnProps) => { +export const TabBtn = ({ label, selected, onClick }: TabBtnProps) => { return (