From cf3fff8340ed713b039485d62f3ff7b5f103b463 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Thu, 29 Jan 2026 17:21:55 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=EA=B8=B0=EC=97=85=EB=B3=84=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=B6=88=EB=9F=AC=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/icons/eye.svg | 3 +- src/index.css | 9 ---- src/pages/home/HomePage.tsx | 97 +++++++++++++++++++++++++++++++------ src/shared/CardItem.tsx | 80 ++++++++++++++++++++++-------- src/types/post.ts | 38 +++++++++++++++ 5 files changed, 181 insertions(+), 46 deletions(-) create mode 100644 src/types/post.ts diff --git a/src/assets/icons/eye.svg b/src/assets/icons/eye.svg index 4ff026c..4eee05e 100644 --- a/src/assets/icons/eye.svg +++ b/src/assets/icons/eye.svg @@ -1,5 +1,4 @@ - + - diff --git a/src/index.css b/src/index.css index 24d8a4c..d8a458c 100644 --- a/src/index.css +++ b/src/index.css @@ -185,15 +185,6 @@ } } -html { - -ms-overflow-style: none; /* IE, Edge */ - scrollbar-width: none; /* Firefox */ - overscroll-behavior: none; -} -html::-webkit-scrollbar { - display: none; /* Chrome, Safari, Opera */ -} - .scrollbar-hide::-webkit-scrollbar { display: none; } diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index fdc6615..8a943b4 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -5,7 +5,7 @@ import Restart from "@/assets/icons/restart.svg"; import { CardItem } from "../../shared/CardItem"; import { CompanyItem } from "./components/CompanyItem"; -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { CompaniesModal } from "./components/CompaniesModal"; import { HomeCompanySelectBtn } from "./components/HomeCompanySelectBtn"; import { useCompanyStore } from "../../store/uesCompanyStore"; @@ -13,6 +13,12 @@ import { MockData } from "../../Mock/company"; import { useTagStore } from "../../store/useTagStore"; import { SelectionBtn } from "../../shared/select-button/SelectionBtn"; import { TAB_MAP } from "../../constants/tab"; +import { + useSuspenseInfiniteQuery, + type QueryFunctionContext, +} from "@tanstack/react-query"; +import api from "../../lib/api"; +import type { CardItemProps, PostResponseDto } from "../../types/post"; export const HomePage = () => { const [selectedTab, setSelectedTab] = useState(0); // 0 = 기업별 게시글 @@ -20,10 +26,69 @@ export const HomePage = () => { const { companies, toggleCompany } = useCompanyStore(); // console.log(companies); + const infiniteRef = useRef(null); const { tag } = useTagStore(); - console.log(tag); + // console.log(tag); + type PageParamType = { + lastPublishedAt?: string; + lastPostId?: number; + }; + const getData = async ({ + pageParam, + }: QueryFunctionContext<["posts", "recent", "latest"], PageParamType>) => { + const res = await api.get("/api/v2/posts/recent", { + params: { + sortBy: "LATEST", + size: 20, + ...pageParam, + }, + }); + return res.data; + }; - const maxCompany = MockData.data.slice(0, 8); //최대 8개까지 + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + useSuspenseInfiniteQuery< + PostResponseDto, // queryFn + Error, + PostResponseDto[], // select ㅌ타입 + ["posts", "recent", "latest"], + PageParamType + >({ + queryKey: ["posts", "recent", "latest"], + queryFn: getData, + initialPageParam: {}, + getNextPageParam: lastPage => { + if (!lastPage.data || !lastPage.data.hasNext) return undefined; + return { + lastPublishedAt: lastPage.data.lastPublishedAt, + lastPostId: lastPage.data.lastPostId, + }; + }, + select: res => res.pages, + }); + + useEffect(() => { + if (!infiniteRef.current || !hasNextPage) return; + + const observer = new IntersectionObserver(entries => { + console.log(entries[0].isIntersecting); + if (entries[0].isIntersecting && !isFetchingNextPage) { + fetchNextPage(); + } + }); + + observer.observe(infiniteRef.current); + + return () => observer.disconnect(); + }, [fetchNextPage, hasNextPage, isFetchingNextPage]); + + console.log(data); + const posts: CardItemProps[] = data.flatMap( + (page: PostResponseDto) => page.data.posts, + ); //게시글 + console.log(data); + + const maxCompany = MockData.data.slice(0, 8); return (
setModal(false)}> @@ -113,18 +178,22 @@ export const HomePage = () => { )} +
); }; diff --git a/src/shared/CardItem.tsx b/src/shared/CardItem.tsx index ae7b45e..8c1740b 100644 --- a/src/shared/CardItem.tsx +++ b/src/shared/CardItem.tsx @@ -1,25 +1,63 @@ import BookOn from "@/assets/icons/book-on.svg"; import Eye from "@/assets/icons/eye.svg"; -import User from "@/assets/images/user.png"; +import { forwardRef } from "react"; -// interface CardItemProps { -// image?: string; -// } +interface CardItemProps { + id?: number; + title: string; + company: string; + url?: string; + logoUrl: string; + thumbnailUrl: string; + publishedAt?: string; + viewCount: number; + keywords?: string[]; +} -export const CardItem = () => { - return ( -
  • -
    - - -
    -
    -

    Title

    -

    - 배포주절주절ㅇㅇㅇㅇㅇㅇㅇdddddddㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇㅇ -

    -
    - -
  • - ); -}; +export const CardItem = forwardRef( + ({ viewCount, logoUrl, title, thumbnailUrl, company, url }, ref) => { + return ( +
  • +
    + {company} + 북마크 +
    +
    +

    + {title} +

    + {thumbnailUrl ? ( + 썸네일 + ) : ( +

    + 이 글은 유튜브에서 성공적으로 콘텐츠를 제작하고 비즈니스 + 플랫폼으로 활용하기 위한 전략을 다룹니다. 많은 크리에이터들이 + 자신의 영상이 성공하지 않는 이유에 대해 고민하지만, 대중이 원하는 + 콘텐츠는 명확히 존재합니다. +
    이를 파악하고 제작하는 것이 중요하며, 성공적인 유튜브 채널 + 운영을 위해서는 타겟 청중을 이해하고 그들의 관심사에 맞춘 콘텐츠를 + 제공해야 합니다.
    + 또한, 유튜브 알고리즘을 이해하고 활용하는 것이 필수적이며, SEO + 최적화와 꾸준한 업로드 일정이 성공의 열쇠가 됩니다. 이 글은 이러한 + 요소들을 종합적으로 분석하고, 크리에이터들이 실질적으로 적용할 수 + 있는 팁과 전략을 제시합니다. +

    + )} +
    +
    + +

    {viewCount}

    +
    + +
  • + ); + }, +); diff --git a/src/types/post.ts b/src/types/post.ts new file mode 100644 index 0000000..72c2808 --- /dev/null +++ b/src/types/post.ts @@ -0,0 +1,38 @@ +// 페이지 게시글 응답 DTO +export type PostParamsDto = { + sortBy: string; + lastViewCount: number; + lastPublishedAt?: string; + lastPostId?: number; + size: number; +}; + +//페이지 단위 무한스클롤 응답 +export type PostResponseDto = { + data: PostListResponse; + code: string; + isSuccess: boolean; + message: string; +}; + +//페이지 내부 게시글 응답 리스트 타입 +type PostListResponse = { + hasNext: boolean; + lastPostId: number; + lastPublishedAt: string; + lastViewCount: number; + posts: CardItemProps[]; +}; + +//card Item +export type CardItemProps = { + id?: number; + title: string; + company: string; + url?: string; + logoUrl: string; + thumbnailUrl: string; + publishedAt?: string; + viewCount: number; + keywords?: string[]; +}; From 70f399a00bec29060f402791c6831daeb1f4ff00 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Thu, 29 Jan 2026 23:07:55 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=EA=B8=B0=EC=97=85=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=A1=B0=ED=9A=8C=ED=95=98=EA=B8=B0=20api?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useGetInfinitePostList.ts | 53 ++++++++++ src/lib/company.ts | 15 +++ src/lib/post.ts | 20 ++++ src/pages/home/HomePage.tsx | 98 +++++++------------ src/pages/home/components/CompaniesModal.tsx | 24 +++-- src/pages/home/components/CompanyItem.tsx | 6 +- .../home/components/CompanyModalItem.tsx | 6 +- .../home/components/HomeCompanySelectBtn.tsx | 5 +- src/types/company.ts | 10 ++ 9 files changed, 156 insertions(+), 81 deletions(-) create mode 100644 src/hooks/useGetInfinitePostList.ts create mode 100644 src/lib/company.ts create mode 100644 src/lib/post.ts create mode 100644 src/types/company.ts diff --git a/src/hooks/useGetInfinitePostList.ts b/src/hooks/useGetInfinitePostList.ts new file mode 100644 index 0000000..767536d --- /dev/null +++ b/src/hooks/useGetInfinitePostList.ts @@ -0,0 +1,53 @@ +// src/hooks/useInfinitePosts.ts +import { + useSuspenseInfiniteQuery, + type QueryFunctionContext, +} from "@tanstack/react-query"; +import type { PostResponseDto } from "../types/post"; +import { getPostList } from "../lib/post"; + +export type PageParamType = { + lastPublishedAt?: string; + lastPostId?: number; +}; + +interface UseInfinitePostsParams { + sortBy: "LATEST" | "POPULAR"; + size?: number; +} + +export const useInfinitePosts = ({ + sortBy, + size = 20, +}: UseInfinitePostsParams) => { + return useSuspenseInfiniteQuery< + PostResponseDto, // queryFn return + Error, // error + PostResponseDto[], // select result + ["posts", typeof sortBy], // queryKey + PageParamType + >({ + queryKey: ["posts", sortBy], + queryFn: ({ + pageParam, + }: QueryFunctionContext<["posts", typeof sortBy], PageParamType>) => + getPostList({ + sortBy, + size, + ...pageParam, + }), + + initialPageParam: {}, + + getNextPageParam: lastPage => { + if (!lastPage.data?.hasNext) return undefined; + + return { + lastPublishedAt: lastPage.data.lastPublishedAt, + lastPostId: lastPage.data.lastPostId, + }; + }, + + select: res => res.pages, + }); +}; diff --git a/src/lib/company.ts b/src/lib/company.ts new file mode 100644 index 0000000..516f262 --- /dev/null +++ b/src/lib/company.ts @@ -0,0 +1,15 @@ +import { useSuspenseQuery } from "@tanstack/react-query"; +import api from "./api"; + +export const getCompanyList = async () => { + const { data } = await api.get("/api/v2/posts/companies"); + return data; +}; + +export const useGetCompany = () => { + return useSuspenseQuery({ + queryFn: getCompanyList, + queryKey: ["company"], + select: res => res.data, + }); +}; diff --git a/src/lib/post.ts b/src/lib/post.ts new file mode 100644 index 0000000..db410d9 --- /dev/null +++ b/src/lib/post.ts @@ -0,0 +1,20 @@ +// 최근 게시글 , 인기있는 게시글 + +import type { PostResponseDto } from "../types/post"; +import api from "./api"; + +export interface GetPostListParams { + sortBy: "LATEST" | "POPULAR"; + size?: number; + lastPublishedAt?: string; + lastPostId?: number; +} +export const getPostList = async ( + params: GetPostListParams, +): Promise => { + const res = await api.get("/api/v2/posts/recent", { + params, + }); + + return res.data; +}; diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 8a943b4..9176f5e 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -2,93 +2,55 @@ import { TabSelectList } from "./components/TabSelectList"; import PopOn from "@/assets/icons/pop-on.svg"; import PopOff from "@/assets/icons/pop-off.svg"; import Restart from "@/assets/icons/restart.svg"; - import { CardItem } from "../../shared/CardItem"; import { CompanyItem } from "./components/CompanyItem"; import { useEffect, useRef, useState } from "react"; import { CompaniesModal } from "./components/CompaniesModal"; import { HomeCompanySelectBtn } from "./components/HomeCompanySelectBtn"; import { useCompanyStore } from "../../store/uesCompanyStore"; -import { MockData } from "../../Mock/company"; import { useTagStore } from "../../store/useTagStore"; import { SelectionBtn } from "../../shared/select-button/SelectionBtn"; import { TAB_MAP } from "../../constants/tab"; -import { - useSuspenseInfiniteQuery, - type QueryFunctionContext, -} from "@tanstack/react-query"; -import api from "../../lib/api"; + import type { CardItemProps, PostResponseDto } from "../../types/post"; +import { useInfinitePosts } from "../../hooks/useGetInfinitePostList"; +import { useGetCompany } from "../../lib/company"; +import type { CompanyType } from "../../types/company"; export const HomePage = () => { const [selectedTab, setSelectedTab] = useState(0); // 0 = 기업별 게시글 const [modal, setModal] = useState(false); const { companies, toggleCompany } = useCompanyStore(); - // console.log(companies); + + //회사 불러오기 + const { data: companyData } = useGetCompany(); + console.log(companyData); const infiniteRef = useRef(null); const { tag } = useTagStore(); - // console.log(tag); - type PageParamType = { - lastPublishedAt?: string; - lastPostId?: number; - }; - const getData = async ({ - pageParam, - }: QueryFunctionContext<["posts", "recent", "latest"], PageParamType>) => { - const res = await api.get("/api/v2/posts/recent", { - params: { - sortBy: "LATEST", - size: 20, - ...pageParam, - }, - }); - return res.data; - }; - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = - useSuspenseInfiniteQuery< - PostResponseDto, // queryFn - Error, - PostResponseDto[], // select ㅌ타입 - ["posts", "recent", "latest"], - PageParamType - >({ - queryKey: ["posts", "recent", "latest"], - queryFn: getData, - initialPageParam: {}, - getNextPageParam: lastPage => { - if (!lastPage.data || !lastPage.data.hasNext) return undefined; - return { - lastPublishedAt: lastPage.data.lastPublishedAt, - lastPostId: lastPage.data.lastPostId, - }; - }, - select: res => res.pages, + useInfinitePosts({ + sortBy: selectedTab === 2 ? "LATEST" : "POPULAR", + size: 20, }); useEffect(() => { if (!infiniteRef.current || !hasNextPage) return; - const observer = new IntersectionObserver(entries => { console.log(entries[0].isIntersecting); if (entries[0].isIntersecting && !isFetchingNextPage) { fetchNextPage(); } }); - observer.observe(infiniteRef.current); - return () => observer.disconnect(); }, [fetchNextPage, hasNextPage, isFetchingNextPage]); - console.log(data); const posts: CardItemProps[] = data.flatMap( (page: PostResponseDto) => page.data.posts, ); //게시글 - console.log(data); - - const maxCompany = MockData.data.slice(0, 8); + console.log(selectedTab); + const maxCompany = companyData.companies.slice(0, 8); return (
    setModal(false)}> @@ -103,26 +65,36 @@ export const HomePage = () => { {selectedTab === 0 && ( <>
    - {companies.length !== 0 && ( + {companyData.companies.length !== 0 && ( <>
    선택된 기업:
    - {companies.map(company => ( - e.stopPropagation()} - /> - ))} + {companies.map(company => { + const matchedCompany = companyData.companies.find( + item => item.company === company, + ); + + return ( + e.stopPropagation()} + /> + ); + })} )}
    {/* 게시글일때 회사 네모item */}
    - {maxCompany.map(item => { + {maxCompany.map((item: CompanyType) => { return ( toggleCompany(item.companies)} + company={item.company} + logoUrl={item.logoUrl} + newDot={item.hasNewPost} + selected={companies.includes(item.company)} + onClick={() => toggleCompany(item.company)} /> ); })} @@ -141,7 +113,7 @@ export const HomePage = () => { onClick={e => e.stopPropagation()} className="absolute top-25 right-40" > - +
    )} diff --git a/src/pages/home/components/CompaniesModal.tsx b/src/pages/home/components/CompaniesModal.tsx index 31a54a2..d5c7379 100644 --- a/src/pages/home/components/CompaniesModal.tsx +++ b/src/pages/home/components/CompaniesModal.tsx @@ -1,9 +1,13 @@ import { CompanyModalItem } from "./CompanyModalItem"; import { useCompanyStore } from "../../../store/uesCompanyStore"; -import { MockData } from "../../../Mock/company"; import { useRef } from "react"; +import type { CompanyResponseDto } from "../../../types/company"; -export const CompaniesModal = () => { +interface CompaniesModalProps { + companyData: CompanyResponseDto; +} + +export const CompaniesModal = ({ companyData }: CompaniesModalProps) => { const { toggleCompany, companies } = useCompanyStore(); const headerRef = useRef(null); const scrollToTop = () => { @@ -15,7 +19,7 @@ export const CompaniesModal = () => { console.log(companies); return (
    @@ -24,20 +28,20 @@ export const CompaniesModal = () => {

    전체 기업

    -

    {MockData.data.length}개

    +

    {companyData.totalNumber}개

    {/* content */}
    - {MockData.data.map(item => ( + {companyData.companies.map(item => ( toggleCompany(item.companies)} + key={item.company} + company={item.company} + logoUrl={item.logoUrl} + selected={companies.includes(item.company)} + onClick={() => toggleCompany(item.company)} /> ))}
    diff --git a/src/pages/home/components/CompanyItem.tsx b/src/pages/home/components/CompanyItem.tsx index 8a3fb2c..030b6a0 100644 --- a/src/pages/home/components/CompanyItem.tsx +++ b/src/pages/home/components/CompanyItem.tsx @@ -1,9 +1,8 @@ -import User from "@/assets/images/user.png"; import Dot from "@/assets/icons/dot.svg"; import { cn } from "../../../utils/cn"; interface CompanyItemProps { company?: string; - img?: string; + logoUrl?: string; newDot?: boolean; selected: boolean; onClick?: () => void; @@ -12,6 +11,7 @@ export const CompanyItem = ({ company = "company", newDot = false, selected = true, + logoUrl, onClick, }: CompanyItemProps) => { return ( @@ -26,7 +26,7 @@ export const CompanyItem = ({ )} onClick={onClick} > - company + company

    {company}

    diff --git a/src/pages/home/components/CompanyModalItem.tsx b/src/pages/home/components/CompanyModalItem.tsx index e4124aa..79e2f40 100644 --- a/src/pages/home/components/CompanyModalItem.tsx +++ b/src/pages/home/components/CompanyModalItem.tsx @@ -1,14 +1,14 @@ -import User from "@/assets/images/user.png"; import { cn } from "../../../utils/cn"; interface CompanyModalItemProps { company?: string; - img?: string; + logoUrl?: string; selected: boolean; onClick: () => void; } export const CompanyModalItem = ({ company = "company", selected = false, + logoUrl, onClick, }: CompanyModalItemProps) => { return ( @@ -20,7 +20,7 @@ export const CompanyModalItem = ({ onClick={onClick} >
    - company + company

    void; + logoUrl: string; } export const HomeCompanySelectBtn = ({ company, onClick, + logoUrl, }: HomeCompanySelectBtnProps) => { const { toggleCompany } = useCompanyStore(); @@ -18,7 +19,7 @@ export const HomeCompanySelectBtn = ({ className="flex gap-[6px] items-center py-2 px-3 border border-bgNormal bg-white w-fit rounded-[20px]" onClick={onClick} > - company + company

    {company}

    Date: Fri, 30 Jan 2026 00:55:24 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=EA=B8=B0=EC=97=85=EB=B3=84=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useGetInfiniteCompaniesList.ts | 35 ++++++++++++++ src/hooks/useGetInfinitePostList.ts | 7 +-- src/lib/company.ts | 1 + src/lib/post.ts | 18 +++++++ src/pages/home/HomePage.tsx | 61 +++++++++++++++--------- src/types/post.ts | 5 ++ 6 files changed, 98 insertions(+), 29 deletions(-) create mode 100644 src/hooks/useGetInfiniteCompaniesList.ts diff --git a/src/hooks/useGetInfiniteCompaniesList.ts b/src/hooks/useGetInfiniteCompaniesList.ts new file mode 100644 index 0000000..50c2ae6 --- /dev/null +++ b/src/hooks/useGetInfiniteCompaniesList.ts @@ -0,0 +1,35 @@ +import { useSuspenseInfiniteQuery } from "@tanstack/react-query"; +import { getCompaniesPostList } from "../lib/post"; + +interface UseInfiniteCompaniesPostsParams { + companies: string[]; + size?: number; +} + +export const useInfiniteCompaniesPosts = ({ + companies, + size = 20, +}: UseInfiniteCompaniesPostsParams) => { + return useSuspenseInfiniteQuery({ + queryKey: ["posts", "companies", companies], + queryFn: ({ pageParam }) => + getCompaniesPostList({ + companies, + size, + ...pageParam, + }), + + initialPageParam: {}, + + getNextPageParam: lastPage => { + if (!lastPage.data?.hasNext) return undefined; + + return { + lastPublishedAt: lastPage.data.lastPublishedAt, + lastPostId: lastPage.data.lastPostId, + }; + }, + + select: res => res.pages, + }); +}; diff --git a/src/hooks/useGetInfinitePostList.ts b/src/hooks/useGetInfinitePostList.ts index 767536d..c23c53b 100644 --- a/src/hooks/useGetInfinitePostList.ts +++ b/src/hooks/useGetInfinitePostList.ts @@ -3,14 +3,9 @@ import { useSuspenseInfiniteQuery, type QueryFunctionContext, } from "@tanstack/react-query"; -import type { PostResponseDto } from "../types/post"; +import type { PageParamType, PostResponseDto } from "../types/post"; import { getPostList } from "../lib/post"; -export type PageParamType = { - lastPublishedAt?: string; - lastPostId?: number; -}; - interface UseInfinitePostsParams { sortBy: "LATEST" | "POPULAR"; size?: number; diff --git a/src/lib/company.ts b/src/lib/company.ts index 516f262..d7cb326 100644 --- a/src/lib/company.ts +++ b/src/lib/company.ts @@ -1,6 +1,7 @@ import { useSuspenseQuery } from "@tanstack/react-query"; import api from "./api"; +//게시글이 있는 회사 목록 조회 export const getCompanyList = async () => { const { data } = await api.get("/api/v2/posts/companies"); return data; diff --git a/src/lib/post.ts b/src/lib/post.ts index db410d9..59b3126 100644 --- a/src/lib/post.ts +++ b/src/lib/post.ts @@ -18,3 +18,21 @@ export const getPostList = async ( return res.data; }; + +//기업별 게시글 조회 + +export interface GetCompaniesPostListParams { + companies?: string[]; + size?: number; + lastPublishedAt?: string; + lastPostId?: number; +} +export const getCompaniesPostList = async ( + params: GetCompaniesPostListParams, +): Promise => { + const res = await api.get("/api/v2/posts/by-company", { + params, + }); + + return res.data; +}; diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 9176f5e..b20e392 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -16,23 +16,46 @@ import type { CardItemProps, PostResponseDto } from "../../types/post"; import { useInfinitePosts } from "../../hooks/useGetInfinitePostList"; import { useGetCompany } from "../../lib/company"; import type { CompanyType } from "../../types/company"; +import { useInfiniteCompaniesPosts } from "../../hooks/useGetInfiniteCompaniesList"; +import { useGetMyInterest } from "../../lib/my"; +import { TagCodeToLabel } from "../../utils/tagCodeToLabel"; export const HomePage = () => { const [selectedTab, setSelectedTab] = useState(0); // 0 = 기업별 게시글 const [modal, setModal] = useState(false); const { companies, toggleCompany } = useCompanyStore(); + // console.log(companies); + + const companyQuery = useInfiniteCompaniesPosts({ + companies, + }); + + const recentQuery = useInfinitePosts({ + sortBy: "LATEST", + }); + + const popularQuery = useInfinitePosts({ + sortBy: "POPULAR", + }); + + const activeQuery = + selectedTab === 0 + ? companyQuery + : selectedTab === 2 + ? recentQuery + : popularQuery; //회사 불러오기 const { data: companyData } = useGetCompany(); - console.log(companyData); + // console.log(companyData); const infiniteRef = useRef(null); const { tag } = useTagStore(); - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = - useInfinitePosts({ - sortBy: selectedTab === 2 ? "LATEST" : "POPULAR", - size: 20, - }); + + //최근 생성된 게시글 + 인기순 게시글 + 기업별 게시글 + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = activeQuery; + const { data: myInterest } = useGetMyInterest(); + console.log(myInterest); useEffect(() => { if (!infiniteRef.current || !hasNextPage) return; @@ -46,10 +69,11 @@ export const HomePage = () => { return () => observer.disconnect(); }, [fetchNextPage, hasNextPage, isFetchingNextPage]); + //데이터 const posts: CardItemProps[] = data.flatMap( (page: PostResponseDto) => page.data.posts, ); //게시글 - console.log(selectedTab); + // console.log(selectedTab); const maxCompany = companyData.companies.slice(0, 8); return ( @@ -125,21 +149,13 @@ export const HomePage = () => { <>

    나의 관심 분야:

    - - 전체 - React - TypeScript - 전체 - 전체 - React - TypeScript - 전체 - 전체 - React - - {tag.map(item => { - return {item}; - })} + {myInterest.map(item => + TagCodeToLabel(item.category, item.keywords).map(label => ( + + {label} + + )), + )}
    From 261ab8c4b2981c26cd07c34569203ff91396f2b4 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 30 Jan 2026 01:50:24 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat:=EC=B6=94=EC=B2=9C=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=83=88=EB=A1=9C=EA=B3=A0=EC=B9=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/recommendation.ts | 14 ++++++++-- src/pages/home/HomePage.tsx | 51 +++++++++++++++++++++++++------------ 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/lib/recommendation.ts b/src/lib/recommendation.ts index f3e52a5..3c567a1 100644 --- a/src/lib/recommendation.ts +++ b/src/lib/recommendation.ts @@ -1,4 +1,8 @@ -import { useMutation, useSuspenseQuery } from "@tanstack/react-query"; +import { + useMutation, + useQueryClient, + useSuspenseQuery, +} from "@tanstack/react-query"; import api from "./api"; //추천 게시글 조회 @@ -22,9 +26,15 @@ export const postRecommendList = async () => { }; export const usePostRecommendPostList = () => { + const queryClient = useQueryClient(); return useMutation({ mutationFn: postRecommendList, - onSuccess: () => console.log("새로고침성공"), + onSuccess: async () => { + console.log("성공"); + await queryClient.refetchQueries({ + queryKey: ["my", "recommend"], + }); + }, onError: err => { console.log(err); }, diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index 2ec4030..b9612e8 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -8,7 +8,6 @@ import { useEffect, useRef, useState } from "react"; import { CompaniesModal } from "./components/CompaniesModal"; import { HomeCompanySelectBtn } from "./components/HomeCompanySelectBtn"; import { useCompanyStore } from "../../store/uesCompanyStore"; -import { useTagStore } from "../../store/useTagStore"; import { SelectionBtn } from "../../shared/select-button/SelectionBtn"; import { TAB_MAP } from "../../constants/tab"; @@ -42,15 +41,21 @@ export const HomePage = () => { sortBy: "POPULAR", }); - const activeQuery = - selectedTab === 0 - ? companyQuery - : selectedTab === 2 - ? recentQuery - : popularQuery; - - const { data: recommedData } = useGetRecommendPostList(); - console.log(recommedData); + const activeQuery = (() => { + switch (selectedTab) { + case 0: + return companyQuery; + case 1: + return null; // 추천은 무한스크롤 ㄴ + case 2: + return recentQuery; + case 3: + return popularQuery; + } + })(); + + const { data: recommendData } = useGetRecommendPostList(); + // console.log(recommendData); //회사 불러오기 const { data: companyData } = useGetCompany(); @@ -58,14 +63,23 @@ export const HomePage = () => { const infiniteRef = useRef(null); //최근 생성된 게시글 + 인기순 게시글 + 기업별 게시글 - const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = activeQuery; + // const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = activeQuery; + + const isInfiniteTab = selectedTab !== 1; + + const infiniteQuery = isInfiniteTab ? activeQuery : null; + + const data = infiniteQuery?.data; + const fetchNextPage = infiniteQuery?.fetchNextPage; + const hasNextPage = infiniteQuery?.hasNextPage; + const isFetchingNextPage = infiniteQuery?.isFetchingNextPage; + const { data: myInterest } = useGetMyInterest(); // console.log(myInterest); useEffect(() => { if (!infiniteRef.current || !hasNextPage) return; const observer = new IntersectionObserver(entries => { - console.log(entries[0].isIntersecting); if (entries[0].isIntersecting && !isFetchingNextPage) { fetchNextPage(); } @@ -75,10 +89,15 @@ export const HomePage = () => { }, [fetchNextPage, hasNextPage, isFetchingNextPage]); //데이터 - const posts: CardItemProps[] = data.flatMap( - (page: PostResponseDto) => page.data.posts, - ); //게시글 - // console.log(selectedTab); + const posts: CardItemProps[] = (() => { + if (selectedTab === 1) { + return recommendData?.recommendations ?? []; + } + + return data?.flatMap((page: PostResponseDto) => page.data.posts) ?? []; + })(); + + //게시글 const maxCompany = companyData.companies.slice(0, 8); const { mutate: postRecommendList } = usePostRecommendPostList(); From dbde8733ad4e5c0fc4462cbe5537c336448649d0 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 30 Jan 2026 02:03:06 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20a=EB=A7=81=ED=81=AC=20=EC=83=88?= =?UTF-8?q?=ED=83=AD=EC=9C=BC=EB=A1=9C=20=EC=97=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/CardItem.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/shared/CardItem.tsx b/src/shared/CardItem.tsx index 8c1740b..46e3d67 100644 --- a/src/shared/CardItem.tsx +++ b/src/shared/CardItem.tsx @@ -54,8 +54,10 @@ export const CardItem = forwardRef(
    ); From aa07d6f7bed62890c66acd0f9ab5b34ad7e96951 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 30 Jan 2026 18:30:40 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20api?= =?UTF-8?q?=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/activity.ts | 47 +++++++++++++++++++++++++++++++++++++ src/lib/api.ts | 25 ++++++++++++++++---- src/pages/home/HomePage.tsx | 10 ++++---- src/shared/CardItem.tsx | 28 ++++++++++++++++++++-- 4 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 src/lib/activity.ts diff --git a/src/lib/activity.ts b/src/lib/activity.ts new file mode 100644 index 0000000..a5938f8 --- /dev/null +++ b/src/lib/activity.ts @@ -0,0 +1,47 @@ +//사용자 활동 api + +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import api from "./api"; + +//1. 북마크 추가 +export const postBookmark = async (postId: number) => { + const { data } = await api.post("/api/v1/activities/bookmarks", { postId }); + return data; +}; + +export const usePostBookmark = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (postId: number) => postBookmark(postId), + onSuccess: async () => { + console.log("북마크성공"); + await queryClient.invalidateQueries({ + queryKey: ["posts"], + }); + }, + onError: err => console.log(err), + }); +}; + +//1. 북마크 제거 +export const deleteBookmark = async (postId: number) => { + const { data } = await api.delete("/api/v1/activities/bookmarks", { + data: { postId }, + }); + return data; +}; + +export const useDeleteBookmark = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (postId: number) => deleteBookmark(postId), + onSuccess: () => { + console.log("북마크 삭제"); + queryClient.invalidateQueries({ + queryKey: ["posts"], + }); + }, + onError: err => console.log(err), + }); +}; diff --git a/src/lib/api.ts b/src/lib/api.ts index 3af3651..4891610 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -4,12 +4,29 @@ const api = axios.create({ baseURL: "https://techfork.shop", }); -// const TEMP_TOKEN = import.meta.env.VITE_APP_DEV_TOKEN; +const TEMP_TOKEN = import.meta.env.VITE_APP_DEV_TOKEN; + +// api.interceptors.request.use( +// config => { +// const accessToken = useUserStore.getState().user?.accessToken; +// if (accessToken) { +// config.headers.Authorization = `Bearer ${accessToken}`; +// } + +// return config; +// }, +// error => { +// return Promise.reject(error); +// }, +// ); + +// export default api; + api.interceptors.request.use( config => { - const accessToken = useUserStore.getState().user?.accessToken; - if (accessToken) { - config.headers.Authorization = `Bearer ${accessToken}`; + // const accessToken = useUserStore.getState().user?.accessToken; + if (TEMP_TOKEN) { + config.headers.Authorization = `Bearer ${TEMP_TOKEN}`; } return config; diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index b9612e8..e17990e 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -10,7 +10,6 @@ import { HomeCompanySelectBtn } from "./components/HomeCompanySelectBtn"; import { useCompanyStore } from "../../store/uesCompanyStore"; import { SelectionBtn } from "../../shared/select-button/SelectionBtn"; import { TAB_MAP } from "../../constants/tab"; - import type { CardItemProps, PostResponseDto } from "../../types/post"; import { useInfinitePosts } from "../../hooks/useGetInfinitePostList"; import { useGetCompany } from "../../lib/company"; @@ -55,16 +54,12 @@ export const HomePage = () => { })(); const { data: recommendData } = useGetRecommendPostList(); - // console.log(recommendData); //회사 불러오기 const { data: companyData } = useGetCompany(); const infiniteRef = useRef(null); - //최근 생성된 게시글 + 인기순 게시글 + 기업별 게시글 - // const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = activeQuery; - const isInfiniteTab = selectedTab !== 1; const infiniteQuery = isInfiniteTab ? activeQuery : null; @@ -75,7 +70,6 @@ export const HomePage = () => { const isFetchingNextPage = infiniteQuery?.isFetchingNextPage; const { data: myInterest } = useGetMyInterest(); - // console.log(myInterest); useEffect(() => { if (!infiniteRef.current || !hasNextPage) return; @@ -97,6 +91,8 @@ export const HomePage = () => { return data?.flatMap((page: PostResponseDto) => page.data.posts) ?? []; })(); + console.log(posts); + //게시글 const maxCompany = companyData.companies.slice(0, 8); @@ -205,6 +201,8 @@ export const HomePage = () => { title={item.title} thumbnailUrl={item.thumbnailUrl} url={item.url} + id={item.id} + isBookmarked={item.isBookmarked} /> ); })} diff --git a/src/shared/CardItem.tsx b/src/shared/CardItem.tsx index 46e3d67..31d07f4 100644 --- a/src/shared/CardItem.tsx +++ b/src/shared/CardItem.tsx @@ -1,6 +1,8 @@ import BookOn from "@/assets/icons/book-on.svg"; +import BookOff from "@/assets/icons/book-off.svg"; import Eye from "@/assets/icons/eye.svg"; import { forwardRef } from "react"; +import { useDeleteBookmark, usePostBookmark } from "../lib/activity"; interface CardItemProps { id?: number; @@ -10,17 +12,39 @@ interface CardItemProps { logoUrl: string; thumbnailUrl: string; publishedAt?: string; + isBookmarked: boolean; viewCount: number; keywords?: string[]; } export const CardItem = forwardRef( - ({ viewCount, logoUrl, title, thumbnailUrl, company, url }, ref) => { + ( + { viewCount, logoUrl, title, thumbnailUrl, company, url, id, isBookmarked }, + ref, + ) => { + //북마크 추가 + const handleSubmitPostBookmark = usePostBookmark(); + const handleSubmitDeleteBookmark = useDeleteBookmark(); + const handleClick = e => { + e.stopPropagation(); + e.preventDefault(); + if (isBookmarked) { + handleSubmitDeleteBookmark.mutate(id ?? 0); + } else { + handleSubmitPostBookmark.mutate(id ?? 0); + } + }; + return (
  • {company} - 북마크 + 북마크 handleClick(e)} + />

    From 8538fdff050b1b1e68eba496327d1b2e2fcc57f6 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 30 Jan 2026 20:56:35 +0900 Subject: [PATCH 10/12] =?UTF-8?q?design:=20z-index=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/CardItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/CardItem.tsx b/src/shared/CardItem.tsx index 31d07f4..eab56cd 100644 --- a/src/shared/CardItem.tsx +++ b/src/shared/CardItem.tsx @@ -42,7 +42,7 @@ export const CardItem = forwardRef( 북마크 handleClick(e)} />

    From a8a81b2e521fbcf184e524e4f88dad4e9ada12e6 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 30 Jan 2026 21:45:18 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20api=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useGetInfiniteBookmarkList.ts | 20 ++++++++++ src/lib/activity.ts | 9 +++++ src/pages/mypage/MyInterstListPage.tsx | 50 +++++++++++++++++++------ src/types/post.ts | 24 ++++++++++++ 4 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 src/hooks/useGetInfiniteBookmarkList.ts diff --git a/src/hooks/useGetInfiniteBookmarkList.ts b/src/hooks/useGetInfiniteBookmarkList.ts new file mode 100644 index 0000000..0fdd984 --- /dev/null +++ b/src/hooks/useGetInfiniteBookmarkList.ts @@ -0,0 +1,20 @@ +//북마크 리스트 무한스크롤 +import { useSuspenseInfiniteQuery } from "@tanstack/react-query"; +import { getBookmarkList } from "../lib/activity"; + +export const useInfiniteBookmarkPosts = (size = 20) => { + return useSuspenseInfiniteQuery({ + queryKey: ["posts", "bookmarks"], + queryFn: ({ pageParam }) => + getBookmarkList({ + lastBookmarkId: pageParam, + size, + }), + initialPageParam: undefined, + getNextPageParam: lastPage => { + if (!lastPage.data.hasNext) return undefined; + return lastPage.data.lastBookmarkId; + }, + select: res => res.pages, + }); +}; diff --git a/src/lib/activity.ts b/src/lib/activity.ts index a5938f8..f4ce7cd 100644 --- a/src/lib/activity.ts +++ b/src/lib/activity.ts @@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import api from "./api"; +import type { UseInfiniteBookmarkPostsParams } from "../types/post"; //1. 북마크 추가 export const postBookmark = async (postId: number) => { @@ -45,3 +46,11 @@ export const useDeleteBookmark = () => { onError: err => console.log(err), }); }; + +//북마크 목록 조회 +export const getBookmarkList = async ( + params: UseInfiniteBookmarkPostsParams, +) => { + const { data } = await api.get("/api/v1/activities/bookmarks", { params }); + return data; +}; diff --git a/src/pages/mypage/MyInterstListPage.tsx b/src/pages/mypage/MyInterstListPage.tsx index 0c1bd73..3285e52 100644 --- a/src/pages/mypage/MyInterstListPage.tsx +++ b/src/pages/mypage/MyInterstListPage.tsx @@ -1,11 +1,32 @@ -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { MYPAGE_TAP } from "../../constants/tab"; import { TabSelectList } from "../home/components/TabSelectList"; import { CardItem } from "../../shared/CardItem"; +import { useInfiniteBookmarkPosts } from "../../hooks/useGetInfiniteBookmarkList"; // mypage 관심사 list export const MyIntersListPage = () => { const [selectedTab, setSelected] = useState(0); + const infiniteRef = useRef(null); + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + useInfiniteBookmarkPosts(); + + useEffect(() => { + if (!infiniteRef.current || !hasNextPage) return; + const observer = new IntersectionObserver(entries => { + if (entries[0].isIntersecting && !isFetchingNextPage) { + fetchNextPage(); + } + }); + observer.observe(infiniteRef.current); + return () => observer.disconnect(); + }, [fetchNextPage, hasNextPage, isFetchingNextPage]); + + const posts = data?.flatMap(page => page.data.bookmarks) ?? []; + + console.log(posts); + return (
    { tagList={MYPAGE_TAP} />
      - - - - - - - - - - - + {posts.map(item => { + return ( + + ); + })}
    +
    ); }; diff --git a/src/types/post.ts b/src/types/post.ts index 0b5be5c..4f60106 100644 --- a/src/types/post.ts +++ b/src/types/post.ts @@ -41,3 +41,27 @@ export type PageParamType = { lastPublishedAt?: string; lastPostId?: number; }; + +// 북마크 +export type UseInfiniteBookmarkPostsParams = { + lastBookmarkId?: number; + size: number; +}; + +export type PostListBookmarkResponse = { + bookmarkId: number; + postId: number; + title: string; + url: string; + companyName: string; + logoUrl: string; + publishedAt: string; +}; + +//북마크 단위 +export type PostBookmarkResponseDto = { + data: PostListBookmarkResponse; + code: string; + isSuccess: boolean; + message: string; +}; From 03e768b31d7de38d3b60c5ac76d4a5db8ece0401 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Fri, 30 Jan 2026 22:05:21 +0900 Subject: [PATCH 12/12] =?UTF-8?q?fix:=20typeError=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/api.ts | 2 +- src/pages/home/HomePage.tsx | 16 +++++++++------- src/shared/CardItem.tsx | 7 ++++--- src/types/my.ts | 6 ++++++ src/types/post.ts | 2 ++ 5 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 src/types/my.ts diff --git a/src/lib/api.ts b/src/lib/api.ts index 4891610..bfeed1f 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import useUserStore from "../store/useUserStore"; +// import useUserStore from "../store/useUserStore"; const api = axios.create({ baseURL: "https://techfork.shop", }); diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index e17990e..1dd7e96 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -21,13 +21,12 @@ import { useGetRecommendPostList, usePostRecommendPostList, } from "../../lib/recommendation"; +import type { InterestTypeDto } from "../../types/my"; export const HomePage = () => { const [selectedTab, setSelectedTab] = useState(0); // 0 = 기업별 게시글 const [modal, setModal] = useState(false); const { companies, toggleCompany } = useCompanyStore(); - // console.log(companies); - const companyQuery = useInfiniteCompaniesPosts({ companies, }); @@ -57,6 +56,7 @@ export const HomePage = () => { //회사 불러오기 const { data: companyData } = useGetCompany(); + console.log(companyData); const infiniteRef = useRef(null); @@ -70,9 +70,10 @@ export const HomePage = () => { const isFetchingNextPage = infiniteQuery?.isFetchingNextPage; const { data: myInterest } = useGetMyInterest(); + console.log(myInterest); useEffect(() => { - if (!infiniteRef.current || !hasNextPage) return; + if (!infiniteRef.current || !hasNextPage || !fetchNextPage) return; const observer = new IntersectionObserver(entries => { if (entries[0].isIntersecting && !isFetchingNextPage) { fetchNextPage(); @@ -91,7 +92,7 @@ export const HomePage = () => { return data?.flatMap((page: PostResponseDto) => page.data.posts) ?? []; })(); - console.log(posts); + // console.log(posts); //게시글 const maxCompany = companyData.companies.slice(0, 8); @@ -116,7 +117,7 @@ export const HomePage = () => {
    선택된 기업:
    {companies.map(company => { const matchedCompany = companyData.companies.find( - item => item.company === company, + (item: CompanyType) => item.company === company, ); return ( @@ -171,7 +172,7 @@ export const HomePage = () => { <>

    나의 관심 분야:

    - {myInterest.map(item => + {myInterest.map((item: InterestTypeDto) => TagCodeToLabel(item.category, item.keywords).map(label => ( {label} @@ -192,6 +193,7 @@ export const HomePage = () => {
      {posts.map(item => { + const isSelected = selectedTab === 1; return ( { title={item.title} thumbnailUrl={item.thumbnailUrl} url={item.url} - id={item.id} + id={isSelected ? item.postId : item.id} isBookmarked={item.isBookmarked} /> ); diff --git a/src/shared/CardItem.tsx b/src/shared/CardItem.tsx index eab56cd..ad0bb85 100644 --- a/src/shared/CardItem.tsx +++ b/src/shared/CardItem.tsx @@ -25,13 +25,14 @@ export const CardItem = forwardRef( //북마크 추가 const handleSubmitPostBookmark = usePostBookmark(); const handleSubmitDeleteBookmark = useDeleteBookmark(); - const handleClick = e => { + const handleClick = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); + if (!id) return; if (isBookmarked) { - handleSubmitDeleteBookmark.mutate(id ?? 0); + handleSubmitDeleteBookmark.mutate(id); } else { - handleSubmitPostBookmark.mutate(id ?? 0); + handleSubmitPostBookmark.mutate(id); } }; diff --git a/src/types/my.ts b/src/types/my.ts new file mode 100644 index 0000000..c8bb3f7 --- /dev/null +++ b/src/types/my.ts @@ -0,0 +1,6 @@ +//나와맞는게시글 + +export type InterestTypeDto = { + category: string; + keywords: string[]; +}; diff --git a/src/types/post.ts b/src/types/post.ts index 4f60106..49478e0 100644 --- a/src/types/post.ts +++ b/src/types/post.ts @@ -33,8 +33,10 @@ export type CardItemProps = { logoUrl: string; thumbnailUrl: string; publishedAt?: string; + isBookmarked: boolean; viewCount: number; keywords?: string[]; + postId?: number; }; export type PageParamType = {