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 (
+
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:

,
+ });
+
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:

,
+ });
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:
,
+ });
navigate("/login");
return;
}
@@ -86,7 +91,10 @@ export const CardItem = forwardRef(
}
};
return (
- -
+
-
![]()
{
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 (
-
+
{
)}

{
- if (!isLogin) return navigate("/login");
+ if (!isLogin) {
+ toast.info(`로그인이 sss필요한 서비스입니다.`, {
+ icon:

,
+ });
+ 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 (