From 63dfc4cad8c172293b3dc3c161fb03f9d98f9d4f Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 21 Apr 2026 12:49:16 +0900 Subject: [PATCH 1/6] =?UTF-8?q?refactor:=20=20homePage=20hook=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/home/index.ts | 2 + src/features/home/model/useHomePage.ts | 54 +++++++++ .../home/model/useHomeSearchParams.ts | 27 +++++ src/pages/home/HomePage.tsx | 106 +++++------------- 4 files changed, 113 insertions(+), 76 deletions(-) create mode 100644 src/features/home/model/useHomePage.ts create mode 100644 src/features/home/model/useHomeSearchParams.ts diff --git a/src/features/home/index.ts b/src/features/home/index.ts index 087f77e..14205cf 100644 --- a/src/features/home/index.ts +++ b/src/features/home/index.ts @@ -2,6 +2,8 @@ export { useGetCompany } from "./api/company"; export { usePostRecommendPostList } from "./api/recommendation"; export { MYPAGE_TAP, TAB_MAP } from "./consts/tab"; export { useCompanyStore } from "./model/useCompanyStore"; +export { useHomePage } from "./model/useHomePage"; +export { useHomeSearchParams } from "./model/useHomeSearchParams"; export { CompanyFilterList } from "./ui/CompanyFilterList"; export { default as InterestPage } from "./ui/InterestPage"; export { default as PostCardList } from "./ui/PostCardList"; diff --git a/src/features/home/model/useHomePage.ts b/src/features/home/model/useHomePage.ts new file mode 100644 index 0000000..d5fda38 --- /dev/null +++ b/src/features/home/model/useHomePage.ts @@ -0,0 +1,54 @@ +import useUserStore from "@/shared/model/useUserStore"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import { useGetCompany } from "../api/company"; +import { usePostRecommendPostList } from "../api/recommendation"; +import { useCompanyStore } from "./useCompanyStore"; +import { useHomeSearchParams } from "./useHomeSearchParams"; + +export const useHomePage = () => { + const [modal, setModal] = useState(false); + const { companies, toggleCompany, resetCompanies } = useCompanyStore(); + const { user } = useUserStore(); + const isLogin = !!user?.accessToken; + const navigate = useNavigate(); + const { data: companyData } = useGetCompany(); + const { mutate: postRecommendList, isPending: isRefreshing } = + usePostRecommendPostList(); + const { searchQuery, selectedTab, isSearching, setSelectedTab } = + useHomeSearchParams(); + const maxCompany = companyData?.companies.slice(0, 8) ?? []; + + useEffect(() => { + return () => { + resetCompanies(); + }; + }, [resetCompanies]); + + const handleTabChange = (tab: number) => { + if (tab === 1 && !isLogin) { + toast.info("로그인이 필요한 서비스입니다."); + navigate("/login"); + return; + } + + setSelectedTab(tab); + }; + + return { + modal, + setModal, + companies, + toggleCompany, + isLogin, + companyData, + maxCompany, + searchQuery, + selectedTab, + isSearching, + postRecommendList, + isRefreshing, + handleTabChange, + }; +}; diff --git a/src/features/home/model/useHomeSearchParams.ts b/src/features/home/model/useHomeSearchParams.ts new file mode 100644 index 0000000..a9d44f1 --- /dev/null +++ b/src/features/home/model/useHomeSearchParams.ts @@ -0,0 +1,27 @@ +import { useEffect } from "react"; +import { useSearchParams } from "react-router-dom"; + +export const useHomeSearchParams = () => { + const [searchParams, setSearchParams] = useSearchParams(); + + const searchQuery = searchParams.get("search") ?? ""; + const selectedTab = Number(searchParams.get("tab") ?? 0); + const isSearching = !!searchQuery.trim(); + + useEffect(() => { + if (!searchParams.get("tab") && !searchParams.get("search")) { + setSearchParams({ tab: "0" }, { replace: true }); + } + }, [searchParams, setSearchParams]); + + const setSelectedTab = (tab: number) => { + setSearchParams({ tab: String(tab) }, { replace: true }); + }; + + return { + searchQuery, + selectedTab, + isSearching, + setSelectedTab, + }; +}; diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx index b6504bb..a581a07 100644 --- a/src/pages/home/HomePage.tsx +++ b/src/pages/home/HomePage.tsx @@ -1,20 +1,14 @@ -import Alert from "@/assets/icons/alert2.svg"; import { CompanyFilterList, PostCardList, TAB_MAP, - useCompanyStore, - useGetCompany, - usePostRecommendPostList, + useHomePage, } from "@/features/home"; import { ErrorBoundary } from "@/shared/ui/ErrorBoundary"; import { Loading } from "@/shared/ui/Loading"; import { SkeletonList } from "@/shared/ui/SkeletonList"; -import useUserStore from "@/shared/model/useUserStore"; -import { lazy, Suspense, useEffect, useState } from "react"; +import { lazy, Suspense } from "react"; import { Helmet } from "react-helmet-async"; -import { useNavigate, useSearchParams } from "react-router-dom"; -import { toast } from "react-toastify"; import { TabSelectList } from "@/shared/ui/TabSelectList"; const InterestPage = lazy(() => @@ -27,53 +21,15 @@ const SearchPostList = lazy(() => ); const HomePage = () => { - const [modal, setModal] = useState(false); - const { companies, toggleCompany, resetCompanies } = useCompanyStore(); - const { user } = useUserStore(); - const isLogin = !!user?.accessToken; - const navigate = useNavigate(); - const { data: companyData } = useGetCompany(); - const { mutate: postRecommendList, isPending: isRefreshing } = - usePostRecommendPostList(); - - const maxCompany = companyData?.companies.slice(0, 8) ?? []; - - const [searchParams, setSearchParams] = useSearchParams(); - const searchQuery = searchParams.get("search") ?? ""; - const selectedTab = Number(searchParams.get("tab") ?? 0); - - useEffect(() => { - if (!searchParams.get("tab") && !searchParams.get("search")) { - setSearchParams({ tab: "0" }, { replace: true }); - } - }, []); - - const isSearching = !!searchQuery.trim(); - - const handleTabChange = (tab: number) => { - if (tab === 1 && !isLogin) { - toast.info("로그인이 필요한 서비스입니다.", { - icon: login으로 이동, - }); - navigate("/login"); - return; - } - setSearchParams({ tab: String(tab) }, { replace: true }); - }; - - useEffect(() => { - return () => { - resetCompanies(); - }; - }, []); + const home = useHomePage(); return ( <> - {isSearching - ? `"${searchQuery}" | TechFork` - : `TechFork | ${TAB_MAP[selectedTab]}`} + {home.isSearching + ? `"${home.searchQuery}" | TechFork` + : `TechFork | ${TAB_MAP[home.selectedTab]}`} { content="https://techfork-fe.vercel.app/sub_logo.png" /> -
setModal(false)}> +
home.setModal(false)}> - {isSearching ? ( - <> - - }> - - - - + {home.isSearching ? ( + + }> + + + ) : ( <> - {selectedTab === 0 && ( + {home.selectedTab === 0 && ( <> )} - {/* 나와맞는 게시글 */} - {selectedTab === 1 && isLogin && ( + {home.selectedTab === 1 && home.isLogin && ( }> )} - - {(selectedTab === 2 || selectedTab === 3) && ( - + {[2, 3].includes(home.selectedTab) && ( + )} )} From 17e41d2442410d7a7279ada2adc9977fbcf1a893 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 21 Apr 2026 13:06:56 +0900 Subject: [PATCH 2/6] =?UTF-8?q?design:=20=EC=84=A0=ED=83=9D=20=EC=95=88?= =?UTF-8?q?=EB=90=9C=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/mypage/ui/TagItem.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/features/mypage/ui/TagItem.tsx b/src/features/mypage/ui/TagItem.tsx index 18b6ce2..dbb9085 100644 --- a/src/features/mypage/ui/TagItem.tsx +++ b/src/features/mypage/ui/TagItem.tsx @@ -17,9 +17,13 @@ export const TagItem = ({ tag, selected, length, onClick }: TagItemProps) => { onClick={onClick} >

{tag}

- - {length} - + {length == 0 ? ( + "" + ) : ( + + {length} + + )} ); }; From 65820855eb47cffee79976f12168b2d868f5cd97 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 21 Apr 2026 13:33:14 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EA=B4=80=EC=8B=9C=EC=82=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=B0=8F=20hook=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/mypage/index.ts | 5 + .../model/useEditInterestCategoryStore.ts | 17 ++ .../mypage/model/useEditInterestPage.ts | 58 ++++++ src/features/mypage/ui/EditAsidePage.tsx | 58 ++++++ .../mypage/ui/EditInterestSummary.tsx | 52 +++++ .../mypage/ui/EditInterestTechSection.tsx | 51 +++++ src/pages/mypage/EditInterestPage.tsx | 177 ++---------------- 7 files changed, 256 insertions(+), 162 deletions(-) create mode 100644 src/features/mypage/model/useEditInterestCategoryStore.ts create mode 100644 src/features/mypage/model/useEditInterestPage.ts create mode 100644 src/features/mypage/ui/EditAsidePage.tsx create mode 100644 src/features/mypage/ui/EditInterestSummary.tsx create mode 100644 src/features/mypage/ui/EditInterestTechSection.tsx diff --git a/src/features/mypage/index.ts b/src/features/mypage/index.ts index 253ffd4..c0fef42 100644 --- a/src/features/mypage/index.ts +++ b/src/features/mypage/index.ts @@ -1,9 +1,14 @@ export { usePutMyInterst } from "./api/myEdit"; export { INTERESTS_MOCK } from "./consts/interests"; export { ASK_MAP, MYPAGE_NAV } from "./consts/mypage"; +export { useEditInterestCategoryStore } from "./model/useEditInterestCategoryStore"; export { useEditTagStore } from "./model/useEditTagStore"; +export { useEditInterestPage } from "./model/useEditInterestPage"; export { useInfiniteActivityPosts } from "./model/useInfiniteActivityPosts"; export { AskConfirmModal } from "./ui/AskConfirmModal"; +export { EditAsidePage } from "./ui/EditAsidePage"; +export { EditInterestSummary } from "./ui/EditInterestSummary"; +export { EditInterestTechSection } from "./ui/EditInterestTechSection"; export { InterstBtn } from "./ui/IntersetBtn"; export { LeaveModal } from "./ui/LeaveModal"; export { ProfileEditHeader } from "./ui/ProfileEditHeader"; diff --git a/src/features/mypage/model/useEditInterestCategoryStore.ts b/src/features/mypage/model/useEditInterestCategoryStore.ts new file mode 100644 index 0000000..23995dc --- /dev/null +++ b/src/features/mypage/model/useEditInterestCategoryStore.ts @@ -0,0 +1,17 @@ +import { INTERESTS_MOCK } from "@/features/mypage/consts/interests"; +import { create } from "zustand"; + +interface EditInterestCategoryStoreProps { + selectedCategory: string; + setSelectedCategory: (categoryCode: string) => void; + resetSelectedCategory: () => void; +} + +export const useEditInterestCategoryStore = + create(set => ({ + selectedCategory: INTERESTS_MOCK.interests[0].code, + setSelectedCategory: categoryCode => + set({ selectedCategory: categoryCode }), + resetSelectedCategory: () => + set({ selectedCategory: INTERESTS_MOCK.interests[0].code }), + })); diff --git a/src/features/mypage/model/useEditInterestPage.ts b/src/features/mypage/model/useEditInterestPage.ts new file mode 100644 index 0000000..766b47d --- /dev/null +++ b/src/features/mypage/model/useEditInterestPage.ts @@ -0,0 +1,58 @@ +import { useGetMyInterest } from "@/shared/api/my"; +import { useEffect } from "react"; +import { usePutMyInterst } from "../api/myEdit"; +import { useEditInterestCategoryStore } from "./useEditInterestCategoryStore"; +import { useEditTagStore } from "./useEditTagStore"; + +export const useEditInterestPage = () => { + const { selectedTags, setFromServer, originalTags, resetTag } = + useEditTagStore(); + const { data } = useGetMyInterest(); + const resetSelectedCategory = useEditInterestCategoryStore( + state => state.resetSelectedCategory, + ); + const handleSaveInterest = usePutMyInterst(); + + useEffect(() => { + if (originalTags.length === 0) { + const serverTags = data.flatMap(item => + item.keywords.map(keywordCode => `${item.category}:${keywordCode}`), + ); + setFromServer(serverTags); + } + }, [data, originalTags.length, setFromServer]); + + useEffect(() => { + return () => { + resetTag(); + resetSelectedCategory(); + }; + }, [resetSelectedCategory, resetTag]); + + const handleSave = () => { + const categoryMap: Record = {}; + + selectedTags.forEach(code => { + const [categoryCode, keywordCode] = code.split(":"); + if (!categoryCode || !keywordCode) return; + + if (!categoryMap[categoryCode]) { + categoryMap[categoryCode] = []; + } + + categoryMap[categoryCode].push(keywordCode); + }); + + handleSaveInterest.mutate({ + interests: Object.entries(categoryMap).map(([category, keywords]) => ({ + category, + keywords, + })), + }); + }; + + return { + modalSaving: handleSaveInterest.isPending, + handleSave, + }; +}; diff --git a/src/features/mypage/ui/EditAsidePage.tsx b/src/features/mypage/ui/EditAsidePage.tsx new file mode 100644 index 0000000..41698ea --- /dev/null +++ b/src/features/mypage/ui/EditAsidePage.tsx @@ -0,0 +1,58 @@ +import { INTERESTS_MOCK } from "@/features/mypage/consts/interests"; +import { useMemo, useRef } from "react"; +import { useEditInterestCategoryStore } from "../model/useEditInterestCategoryStore"; +import { useEditTagStore } from "../model/useEditTagStore"; +import { TagItem } from "./TagItem"; + +export const EditAsidePage = () => { + const scrollRef = useRef(null); + const selectedTags = useEditTagStore(state => state.selectedTags); + const selectedCategory = useEditInterestCategoryStore( + state => state.selectedCategory, + ); + const setSelectedCategory = useEditInterestCategoryStore( + state => state.setSelectedCategory, + ); + const myInterestMap = useMemo( + () => + selectedTags.reduce>((acc, code) => { + const [categoryCode] = code.split(":"); + if (!categoryCode) return acc; + acc[categoryCode] = (acc[categoryCode] ?? 0) + 1; + return acc; + }, {}), + [selectedTags], + ); + + const scrollToTop = () => { + scrollRef.current?.scrollIntoView({ behavior: "smooth", block: "center" }); + }; + + return ( + + ); +}; diff --git a/src/features/mypage/ui/EditInterestSummary.tsx b/src/features/mypage/ui/EditInterestSummary.tsx new file mode 100644 index 0000000..ae86d3a --- /dev/null +++ b/src/features/mypage/ui/EditInterestSummary.tsx @@ -0,0 +1,52 @@ +import { cn } from "@/shared/lib/cn"; +import { tagCodeToLabel } from "@/shared/lib/tagCodeToLabel"; +import { useMemo } from "react"; +import { useEditTagStore } from "../model/useEditTagStore"; +import { InterstBtn } from "./IntersetBtn"; + +interface EditInterestSummaryProps { + isSaving: boolean; + onSave: () => void; +} + +export const EditInterestSummary = ({ + isSaving, + onSave, +}: EditInterestSummaryProps) => { + const selectedTags = useEditTagStore(state => state.selectedTags); + const originalTags = useEditTagStore(state => state.originalTags); + const isEqual = useMemo( + () => + originalTags.length === selectedTags.length && + originalTags.every(tag => selectedTags.includes(tag)), + [originalTags, selectedTags], + ); + + return ( +
+

선택한 관심사

+ +
+
+ {selectedTags.map(tag => { + const [categoryCode, keywordCode] = tag.split(":"); + const label = + tagCodeToLabel(categoryCode, [keywordCode])[0] ?? keywordCode; + + return ; + })} +
+ +
+
+ ); +}; diff --git a/src/features/mypage/ui/EditInterestTechSection.tsx b/src/features/mypage/ui/EditInterestTechSection.tsx new file mode 100644 index 0000000..f5fea1a --- /dev/null +++ b/src/features/mypage/ui/EditInterestTechSection.tsx @@ -0,0 +1,51 @@ +import { TAG_CATEGORY_MAP, TAG_MAP } from "@/shared/consts/tags"; +import { useMemo } from "react"; +import { INTERESTS_MOCK } from "../consts/interests"; +import { useEditInterestCategoryStore } from "../model/useEditInterestCategoryStore"; +import { useEditTagStore } from "../model/useEditTagStore"; +import { TechSelection } from "./TechSelection"; + +export const EditInterestTechSection = () => { + const selectedTags = useEditTagStore(state => state.selectedTags); + const toggleTag = useEditTagStore(state => state.toggleTag); + const selectedCategory = useEditInterestCategoryStore( + state => state.selectedCategory, + ); + const selectedCategoryData = useMemo( + () => INTERESTS_MOCK.interests.find(item => item.code === selectedCategory), + [selectedCategory], + ); + const selectedCategoryTags = useMemo(() => { + if (!selectedCategoryData) return []; + + return TAG_MAP[ + TAG_CATEGORY_MAP[selectedCategoryData.code as keyof typeof TAG_CATEGORY_MAP] + ]; + }, [selectedCategoryData]); + + return ( +
+
{selectedCategoryData?.label}
+

+ 관심있는 기술을 선택하세요. +

+ +
+ {selectedCategoryTags.map(({ code, label }) => { + const value = `${selectedCategoryData?.code}:${code}`; + + return ( + + selectedCategoryData && toggleTag(selectedCategoryData.code, code) + } + /> + ); + })} +
+
+ ); +}; diff --git a/src/pages/mypage/EditInterestPage.tsx b/src/pages/mypage/EditInterestPage.tsx index 3a9b035..3de0d62 100644 --- a/src/pages/mypage/EditInterestPage.tsx +++ b/src/pages/mypage/EditInterestPage.tsx @@ -1,189 +1,42 @@ -import { TAG_CATEGORY_MAP, TAG_MAP } from "@/shared/consts/tags"; -import { tagCodeToLabel } from "@/shared/lib/tagCodeToLabel"; import { - INTERESTS_MOCK, - InterstBtn, - TagItem, - TechSelection, - useEditTagStore, - usePutMyInterst, + EditAsidePage, + EditInterestSummary, + EditInterestTechSection, + useEditInterestPage, } from "@/features/mypage"; -import { useGetMyInterest } from "@/shared/api/my"; -import { cn } from "@/shared/lib/cn"; - -import { useEffect, useRef, useState } from "react"; import { Helmet } from "react-helmet-async"; const EditInterestPage = () => { - const { selectedTags, setFromServer, toggleTag, originalTags } = - useEditTagStore(); - const { data } = useGetMyInterest(); - - // 카테고리 라벨 맞추기 - const myInterestMap = selectedTags.reduce>( - (acc, code) => { - const [categoryCode] = code.split(":"); - if (!categoryCode) return acc; - acc[categoryCode] = (acc[categoryCode] ?? 0) + 1; - return acc; - }, - {}, - ); - //선택한 동적 카테고리 - const [selectedCategory, setSelectedCategory] = useState( - INTERESTS_MOCK.interests[0].code, - ); - - useEffect(() => { - if (originalTags.length === 0) { - const serverTags = data.flatMap(item => - item.keywords.map(keywordCode => `${item.category}:${keywordCode}`), - ); - setFromServer(serverTags); - } - }, [data, originalTags.length, setFromServer]); - - // 선택된 카테고리 - const selectedCategoryData = INTERESTS_MOCK.interests.find( - item => item.code === selectedCategory, - ); - const selectedCategoryTags = selectedCategoryData - ? TAG_MAP[ - TAG_CATEGORY_MAP[ - selectedCategoryData.code as keyof typeof TAG_CATEGORY_MAP - ] - ] - : []; - - const isEqual = - originalTags.length === selectedTags.length && - originalTags.every(tag => selectedTags.includes(tag)); - - const scrollRef = useRef(null); - const scrollToTop = () => { - scrollRef.current?.scrollIntoView({ behavior: "smooth", block: "center" }); - }; - - const handleSaveInterest = usePutMyInterst(); - - const handleSave = () => { - const categoryMap: Record = {}; - - selectedTags.forEach(code => { - const [categoryCode, keywordCode] = code.split(":"); - if (!categoryMap[categoryCode]) { - categoryMap[categoryCode] = []; - } - categoryMap[categoryCode].push(keywordCode); - }); - - const payload = { - interests: Object.entries(categoryMap).map(([category, keywords]) => ({ - category, - keywords, - })), - }; - handleSaveInterest.mutate(payload); - }; + const editInterest = useEditInterestPage(); return ( <> - 관심사 수정 | TechFork + 관심사 설정 | TechFork
-

관심 분야를 수정해보세요.

+

관심 분야를 설정해보세요.

선택 분야를 바탕으로 맞춤형 게시글을 추천해드려요.
-
-

선택된 관심사

- -
-
- {selectedTags.map(tag => { - const [categoryCode, keywordCode] = tag.split(":"); - const label = - tagCodeToLabel(categoryCode, [keywordCode])[0] ?? - keywordCode; - return ; - })} -
- -
-
-
- - {/* 기술 */} -
-
{selectedCategoryData?.label}
-

- 관심있는 기술을 선택하세요. -

- -
- {selectedCategoryTags.map(({ code, label }) => { - const value = `${selectedCategoryData?.code}:${code}`; - - return ( - - selectedCategoryData && - toggleTag(selectedCategoryData.code, code) - } - /> - ); - })} -
-
+ +
+ +
From ae68afb3ad3e2752f57066ef1b3010a3af448058 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 21 Apr 2026 13:46:02 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EB=AC=B8=EC=9D=98=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B0=8F=20?= =?UTF-8?q?hook=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/mypage/index.ts | 1 + src/features/mypage/model/useAskForm.ts | 44 ++++++++++ src/pages/mypage/AskPage.tsx | 105 ++++++++---------------- 3 files changed, 80 insertions(+), 70 deletions(-) create mode 100644 src/features/mypage/model/useAskForm.ts diff --git a/src/features/mypage/index.ts b/src/features/mypage/index.ts index c0fef42..10fa8bd 100644 --- a/src/features/mypage/index.ts +++ b/src/features/mypage/index.ts @@ -2,6 +2,7 @@ export { usePutMyInterst } from "./api/myEdit"; export { INTERESTS_MOCK } from "./consts/interests"; export { ASK_MAP, MYPAGE_NAV } from "./consts/mypage"; export { useEditInterestCategoryStore } from "./model/useEditInterestCategoryStore"; +export { useAskForm } from "./model/useAskForm"; export { useEditTagStore } from "./model/useEditTagStore"; export { useEditInterestPage } from "./model/useEditInterestPage"; export { useInfiniteActivityPosts } from "./model/useInfiniteActivityPosts"; diff --git a/src/features/mypage/model/useAskForm.ts b/src/features/mypage/model/useAskForm.ts new file mode 100644 index 0000000..d326b9b --- /dev/null +++ b/src/features/mypage/model/useAskForm.ts @@ -0,0 +1,44 @@ +import { useEffect, useState, type MouseEvent } from "react"; + +export const useAskForm = () => { + const [isAsk, setIsAsk] = useState(false); + const [askContent, setAskContent] = useState(""); + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + const [confirmModal, setConfirmModal] = useState(false); + + useEffect(() => { + window.scrollTo({ top: 0, behavior: "smooth" }); + }, []); + + const activeBtn = + title.length > 2 && content.length > 2 && askContent.length > 0; + + const handleAsk = (e: MouseEvent, value: string) => { + e.stopPropagation(); + setAskContent(value); + setIsAsk(false); + }; + + const handleSubmitForm = () => { + setConfirmModal(true); + setTitle(""); + setAskContent(""); + setContent(""); + }; + + return { + isAsk, + askContent, + title, + content, + confirmModal, + activeBtn, + setIsAsk, + setTitle, + setContent, + setConfirmModal, + handleAsk, + handleSubmitForm, + }; +}; diff --git a/src/pages/mypage/AskPage.tsx b/src/pages/mypage/AskPage.tsx index 56fa65f..3e38d78 100644 --- a/src/pages/mypage/AskPage.tsx +++ b/src/pages/mypage/AskPage.tsx @@ -1,49 +1,15 @@ -import { useEffect, useState } from "react"; - import clsx from "clsx"; +import { ASK_MAP, AskConfirmModal, useAskForm } from "@/features/mypage"; import { useGetMyProfile } from "@/shared/api/my"; import { useThemeToggle } from "@/shared/lib/useThemeToggle"; import { InputField } from "@/shared/ui/InputField"; -import { ASK_MAP, AskConfirmModal } from "@/features/mypage"; import { Button } from "@/shared/ui/button/Button"; import { Helmet } from "react-helmet-async"; const AskPage = () => { - useEffect(() => { - window.scrollTo({ top: 0, behavior: "smooth" }); - }, []); - const { data: user } = useGetMyProfile(); - //문의 유형 모달 - const [isAsk, setIsAsk] = useState(false); - //문의하기 - const [askContent, setIsContent] = useState(""); - //제목 - const [title, setTitle] = useState(""); - //문의내용 - const [content, setContent] = useState(""); - // 확인 모달 - const [confirmModal, setIsConfirmModal] = useState(false); - const { isDark } = useThemeToggle(); - - const activeBtn = - title.length > 2 && content.length > 2 && askContent.length > 0; - - console.log(user); - - const handleAsk = (e: React.MouseEvent, value: string) => { - e.stopPropagation(); - setIsContent(value); - setIsAsk(false); - }; - - const handleSubmitForm = () => { - setIsConfirmModal(true); - setTitle(""); - setIsContent(""); - setContent(""); - }; + const askForm = useAskForm(); return ( <> @@ -57,46 +23,48 @@ const AskPage = () => { />
-
-

문의하기

+
+

문의하기

서비스 이용 중 불편한 점이나 문의사항을 남겨주세요.

- {confirmModal ? ( - setIsConfirmModal(false)} /> + {askForm.confirmModal ? ( + askForm.setConfirmModal(false)} /> ) : ( <> -
+
-
setIsAsk(pre => !pre)}> +
askForm.setIsAsk(prev => !prev)} + > - {isAsk && ( + {askForm.isAsk && (
{ASK_MAP.map(askItem => { return (

handleAsk(e, askItem)} + key={askItem} + className="body-r-14 cursor-pointer rounded-xl p-4 hover:bg-[#579AEB]" + onClick={e => askForm.handleAsk(e, askItem)} > {askItem}

@@ -106,43 +74,40 @@ const AskPage = () => { )}
{ - setTitle(e.target.value); + askForm.setTitle(e.target.value); }} disabled={false} /> { - setContent(e.target.value); + askForm.setContent(e.target.value); }} />
-
-

안내사항

-
    -
  • - {" "} - 문의 접수 후 영업일 기준 1~2일 내에 답변드릴 예정입니다. -
  • -
  • 상담은 고객센터(0000-0000)....
  • +
    +

    안내사항

    +
      +
    • 문의 접수 후 영업일 기준 1~2일 내에 답변드릴 예정입니다.
    • +
    • 상담은 고객센터(0000-0000)로도 가능합니다.
    From 5c0ebd8e65aa24fdac93b3488d237d2950d8d5e6 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 21 Apr 2026 13:56:31 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20setting=20page=20const=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EC=9D=B4=EC=9A=A9=EC=95=BD=EA=B4=80=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/mypage/consts/settings.ts | 59 ++++++++++++++++++++ src/features/mypage/index.ts | 1 + src/pages/mypage/SettingPage.tsx | 77 ++++++++------------------ 3 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 src/features/mypage/consts/settings.ts diff --git a/src/features/mypage/consts/settings.ts b/src/features/mypage/consts/settings.ts new file mode 100644 index 0000000..aef06aa --- /dev/null +++ b/src/features/mypage/consts/settings.ts @@ -0,0 +1,59 @@ +import { FileText, Info, LockKeyhole, MessageSquare, Moon } from "lucide-react"; +import type { LucideIcon } from "lucide-react"; + +export interface SettingItem { + icon: LucideIcon; + label: string; + isArrow?: boolean; + isToggle?: boolean; + version?: string; + dark?: boolean; + onClick?: () => void; + onClickDark?: () => void; +} + +interface GetSettingListDataParams { + isDark: boolean; + toggleTheme: () => void; + onAskClick: () => void; + onTerm: () => void; +} + +export const getSettingListData = ({ + isDark, + toggleTheme, + onAskClick, + onTerm, +}: GetSettingListDataParams): SettingItem[] => { + return [ + { + icon: Moon, + label: "다크 모드", + onClickDark: toggleTheme, + dark: isDark, + isToggle: true, + }, + { + icon: Info, + label: "서비스 버전", + version: "1.0.0", + }, + { + icon: FileText, + label: "이용 약관", + isArrow: true, + onClick: onTerm, + }, + { + icon: LockKeyhole, + label: "개인정보 처리방침", + isArrow: true, + }, + { + icon: MessageSquare, + label: "문의 하기", + isArrow: true, + onClick: onAskClick, + }, + ]; +}; diff --git a/src/features/mypage/index.ts b/src/features/mypage/index.ts index 10fa8bd..a414429 100644 --- a/src/features/mypage/index.ts +++ b/src/features/mypage/index.ts @@ -1,6 +1,7 @@ export { usePutMyInterst } from "./api/myEdit"; export { INTERESTS_MOCK } from "./consts/interests"; export { ASK_MAP, MYPAGE_NAV } from "./consts/mypage"; +export { getSettingListData } from "./consts/settings"; export { useEditInterestCategoryStore } from "./model/useEditInterestCategoryStore"; export { useAskForm } from "./model/useAskForm"; export { useEditTagStore } from "./model/useEditTagStore"; diff --git a/src/pages/mypage/SettingPage.tsx b/src/pages/mypage/SettingPage.tsx index 5e32aba..f3290ae 100644 --- a/src/pages/mypage/SettingPage.tsx +++ b/src/pages/mypage/SettingPage.tsx @@ -1,65 +1,34 @@ -import { Moon, Info, FileText, LockKeyhole, MessageSquare } from "lucide-react"; -import type { LucideIcon } from "lucide-react"; -import { useNavigate } from "react-router-dom"; import { useState } from "react"; -import { useGetMyProfile } from "@/shared/api/my"; -import { useThemeToggle } from "@/shared/lib/useThemeToggle"; - import { Helmet } from "react-helmet-async"; +import { useNavigate } from "react-router-dom"; import { + getSettingListData, LeaveModal, ProfileEditHeader, ProfileHeader, SettingList, } from "@/features/mypage"; +import { useGetMyProfile } from "@/shared/api/my"; +import { useThemeToggle } from "@/shared/lib/useThemeToggle"; -interface SettingItem { - icon: LucideIcon; - label: string; - isArrow?: boolean; - isToggle?: boolean; - version?: string; - dark?: boolean; - onClick?: () => void; - onClickDark?: () => void; -} const SettingPage = () => { const navigate = useNavigate(); const [isEdit, setIsEdit] = useState(false); - const [IsModal, setIsModal] = useState(false); + const [isModal, setIsModal] = useState(false); const { data: user } = useGetMyProfile(); const { isDark, toggleTheme } = useThemeToggle(); - const SETTING_LIST_DATA: SettingItem[] = [ - { - icon: Moon, - label: "다크 모드", - onClickDark: toggleTheme, - dark: isDark, - isToggle: true, - }, - { - icon: Info, - label: "서비스 버전", - onClick: () => console.log("다크모드"), - version: "1.0.0", - }, - { - icon: FileText, - label: "이용 약관", - isArrow: true, - }, - { - icon: LockKeyhole, - label: "개인정보 처리방침", - isArrow: true, - }, - { - icon: MessageSquare, - label: "문의 하기", - isArrow: true, - onClick: () => navigate("/ask"), + const settingListData = getSettingListData({ + isDark, + toggleTheme, + onAskClick: () => navigate("/ask"), + onTerm: () => { + window.open( + "https://www.notion.so/lily-reptile-b33/334d0aa99dcf8066a9abc2e9a2aca4fd", + "_blank", + "noopener,noreferrer", + ); }, - ]; + }); return ( <> @@ -69,12 +38,12 @@ const SettingPage = () => {
    -
    +
    {!isEdit ? ( setIsEdit(true)} @@ -96,9 +65,9 @@ const SettingPage = () => { )}
    -
    -

    테마 및 서비스 정보

    - {SETTING_LIST_DATA.map(item => ( +
    +

    테마 및 서비스 정보

    + {settingListData.map(item => ( {
    - {IsModal && ( + {isModal && (
    setIsModal(false)} />
    From 28e72e49555b7a5012f4e03bb3e2a88080899371 Mon Sep 17 00:00:00 2001 From: JeonSuna Date: Tue, 21 Apr 2026 13:58:45 +0900 Subject: [PATCH 6/6] =?UTF-8?q?comment:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/home/ui/CompanyFilterList.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/features/home/ui/CompanyFilterList.tsx b/src/features/home/ui/CompanyFilterList.tsx index edbdf8d..18c88f5 100644 --- a/src/features/home/ui/CompanyFilterList.tsx +++ b/src/features/home/ui/CompanyFilterList.tsx @@ -31,7 +31,6 @@ export const CompanyFilterList = ({ ), [companyData.companies], ); - return ( <>
    @@ -64,15 +63,7 @@ export const CompanyFilterList = ({ onClick={() => toggleCompany(item.company)} /> ))} - {/* toggle modal { - e.stopPropagation(); - setModal(pre => !pre); - }} - /> */} +
    { e.stopPropagation();