Skip to content

Commit 3e85890

Browse files
committed
Merge branch 'develop'
2 parents 1496a54 + cd4daa4 commit 3e85890

17 files changed

Lines changed: 540 additions & 375 deletions

src/features/home/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ export { useGetCompany } from "./api/company";
22
export { usePostRecommendPostList } from "./api/recommendation";
33
export { MYPAGE_TAP, TAB_MAP } from "./consts/tab";
44
export { useCompanyStore } from "./model/useCompanyStore";
5+
export { useHomePage } from "./model/useHomePage";
6+
export { useHomeSearchParams } from "./model/useHomeSearchParams";
57
export { CompanyFilterList } from "./ui/CompanyFilterList";
68
export { default as InterestPage } from "./ui/InterestPage";
79
export { default as PostCardList } from "./ui/PostCardList";
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import useUserStore from "@/shared/model/useUserStore";
2+
import { useEffect, useState } from "react";
3+
import { useNavigate } from "react-router-dom";
4+
import { toast } from "react-toastify";
5+
import { useGetCompany } from "../api/company";
6+
import { usePostRecommendPostList } from "../api/recommendation";
7+
import { useCompanyStore } from "./useCompanyStore";
8+
import { useHomeSearchParams } from "./useHomeSearchParams";
9+
10+
export const useHomePage = () => {
11+
const [modal, setModal] = useState(false);
12+
const { companies, toggleCompany, resetCompanies } = useCompanyStore();
13+
const { user } = useUserStore();
14+
const isLogin = !!user?.accessToken;
15+
const navigate = useNavigate();
16+
const { data: companyData } = useGetCompany();
17+
const { mutate: postRecommendList, isPending: isRefreshing } =
18+
usePostRecommendPostList();
19+
const { searchQuery, selectedTab, isSearching, setSelectedTab } =
20+
useHomeSearchParams();
21+
const maxCompany = companyData?.companies.slice(0, 8) ?? [];
22+
23+
useEffect(() => {
24+
return () => {
25+
resetCompanies();
26+
};
27+
}, [resetCompanies]);
28+
29+
const handleTabChange = (tab: number) => {
30+
if (tab === 1 && !isLogin) {
31+
toast.info("로그인이 필요한 서비스입니다.");
32+
navigate("/login");
33+
return;
34+
}
35+
36+
setSelectedTab(tab);
37+
};
38+
39+
return {
40+
modal,
41+
setModal,
42+
companies,
43+
toggleCompany,
44+
isLogin,
45+
companyData,
46+
maxCompany,
47+
searchQuery,
48+
selectedTab,
49+
isSearching,
50+
postRecommendList,
51+
isRefreshing,
52+
handleTabChange,
53+
};
54+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useEffect } from "react";
2+
import { useSearchParams } from "react-router-dom";
3+
4+
export const useHomeSearchParams = () => {
5+
const [searchParams, setSearchParams] = useSearchParams();
6+
7+
const searchQuery = searchParams.get("search") ?? "";
8+
const selectedTab = Number(searchParams.get("tab") ?? 0);
9+
const isSearching = !!searchQuery.trim();
10+
11+
useEffect(() => {
12+
if (!searchParams.get("tab") && !searchParams.get("search")) {
13+
setSearchParams({ tab: "0" }, { replace: true });
14+
}
15+
}, [searchParams, setSearchParams]);
16+
17+
const setSelectedTab = (tab: number) => {
18+
setSearchParams({ tab: String(tab) }, { replace: true });
19+
};
20+
21+
return {
22+
searchQuery,
23+
selectedTab,
24+
isSearching,
25+
setSelectedTab,
26+
};
27+
};

src/features/home/ui/CompanyFilterList.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export const CompanyFilterList = ({
3131
),
3232
[companyData.companies],
3333
);
34-
3534
return (
3635
<>
3736
<div className="flex items-center gap-4 flex-wrap pb-6 font-strong">
@@ -64,15 +63,7 @@ export const CompanyFilterList = ({
6463
onClick={() => toggleCompany(item.company)}
6564
/>
6665
))}
67-
{/* <img
68-
src={modal ? PopOn : PopOff}
69-
alt="toggle modal"
70-
className="mb-6 cursor-pointer "
71-
onClick={e => {
72-
e.stopPropagation();
73-
setModal(pre => !pre);
74-
}}
75-
/> */}
66+
7667
<div
7768
onClick={e => {
7869
e.stopPropagation();
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { FileText, Info, LockKeyhole, MessageSquare, Moon } from "lucide-react";
2+
import type { LucideIcon } from "lucide-react";
3+
4+
export interface SettingItem {
5+
icon: LucideIcon;
6+
label: string;
7+
isArrow?: boolean;
8+
isToggle?: boolean;
9+
version?: string;
10+
dark?: boolean;
11+
onClick?: () => void;
12+
onClickDark?: () => void;
13+
}
14+
15+
interface GetSettingListDataParams {
16+
isDark: boolean;
17+
toggleTheme: () => void;
18+
onAskClick: () => void;
19+
onTerm: () => void;
20+
}
21+
22+
export const getSettingListData = ({
23+
isDark,
24+
toggleTheme,
25+
onAskClick,
26+
onTerm,
27+
}: GetSettingListDataParams): SettingItem[] => {
28+
return [
29+
{
30+
icon: Moon,
31+
label: "다크 모드",
32+
onClickDark: toggleTheme,
33+
dark: isDark,
34+
isToggle: true,
35+
},
36+
{
37+
icon: Info,
38+
label: "서비스 버전",
39+
version: "1.0.0",
40+
},
41+
{
42+
icon: FileText,
43+
label: "이용 약관",
44+
isArrow: true,
45+
onClick: onTerm,
46+
},
47+
{
48+
icon: LockKeyhole,
49+
label: "개인정보 처리방침",
50+
isArrow: true,
51+
},
52+
{
53+
icon: MessageSquare,
54+
label: "문의 하기",
55+
isArrow: true,
56+
onClick: onAskClick,
57+
},
58+
];
59+
};

src/features/mypage/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
export { usePutMyInterst } from "./api/myEdit";
22
export { INTERESTS_MOCK } from "./consts/interests";
33
export { ASK_MAP, MYPAGE_NAV } from "./consts/mypage";
4+
export { getSettingListData } from "./consts/settings";
5+
export { useEditInterestCategoryStore } from "./model/useEditInterestCategoryStore";
6+
export { useAskForm } from "./model/useAskForm";
47
export { useEditTagStore } from "./model/useEditTagStore";
8+
export { useEditInterestPage } from "./model/useEditInterestPage";
59
export { useInfiniteActivityPosts } from "./model/useInfiniteActivityPosts";
610
export { AskConfirmModal } from "./ui/AskConfirmModal";
11+
export { EditAsidePage } from "./ui/EditAsidePage";
12+
export { EditInterestSummary } from "./ui/EditInterestSummary";
13+
export { EditInterestTechSection } from "./ui/EditInterestTechSection";
714
export { InterstBtn } from "./ui/IntersetBtn";
815
export { LeaveModal } from "./ui/LeaveModal";
916
export { ProfileEditHeader } from "./ui/ProfileEditHeader";
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useEffect, useState, type MouseEvent } from "react";
2+
3+
export const useAskForm = () => {
4+
const [isAsk, setIsAsk] = useState(false);
5+
const [askContent, setAskContent] = useState("");
6+
const [title, setTitle] = useState("");
7+
const [content, setContent] = useState("");
8+
const [confirmModal, setConfirmModal] = useState(false);
9+
10+
useEffect(() => {
11+
window.scrollTo({ top: 0, behavior: "smooth" });
12+
}, []);
13+
14+
const activeBtn =
15+
title.length > 2 && content.length > 2 && askContent.length > 0;
16+
17+
const handleAsk = (e: MouseEvent, value: string) => {
18+
e.stopPropagation();
19+
setAskContent(value);
20+
setIsAsk(false);
21+
};
22+
23+
const handleSubmitForm = () => {
24+
setConfirmModal(true);
25+
setTitle("");
26+
setAskContent("");
27+
setContent("");
28+
};
29+
30+
return {
31+
isAsk,
32+
askContent,
33+
title,
34+
content,
35+
confirmModal,
36+
activeBtn,
37+
setIsAsk,
38+
setTitle,
39+
setContent,
40+
setConfirmModal,
41+
handleAsk,
42+
handleSubmitForm,
43+
};
44+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { INTERESTS_MOCK } from "@/features/mypage/consts/interests";
2+
import { create } from "zustand";
3+
4+
interface EditInterestCategoryStoreProps {
5+
selectedCategory: string;
6+
setSelectedCategory: (categoryCode: string) => void;
7+
resetSelectedCategory: () => void;
8+
}
9+
10+
export const useEditInterestCategoryStore =
11+
create<EditInterestCategoryStoreProps>(set => ({
12+
selectedCategory: INTERESTS_MOCK.interests[0].code,
13+
setSelectedCategory: categoryCode =>
14+
set({ selectedCategory: categoryCode }),
15+
resetSelectedCategory: () =>
16+
set({ selectedCategory: INTERESTS_MOCK.interests[0].code }),
17+
}));
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { useGetMyInterest } from "@/shared/api/my";
2+
import { useEffect } from "react";
3+
import { usePutMyInterst } from "../api/myEdit";
4+
import { useEditInterestCategoryStore } from "./useEditInterestCategoryStore";
5+
import { useEditTagStore } from "./useEditTagStore";
6+
7+
export const useEditInterestPage = () => {
8+
const { selectedTags, setFromServer, originalTags, resetTag } =
9+
useEditTagStore();
10+
const { data } = useGetMyInterest();
11+
const resetSelectedCategory = useEditInterestCategoryStore(
12+
state => state.resetSelectedCategory,
13+
);
14+
const handleSaveInterest = usePutMyInterst();
15+
16+
useEffect(() => {
17+
if (originalTags.length === 0) {
18+
const serverTags = data.flatMap(item =>
19+
item.keywords.map(keywordCode => `${item.category}:${keywordCode}`),
20+
);
21+
setFromServer(serverTags);
22+
}
23+
}, [data, originalTags.length, setFromServer]);
24+
25+
useEffect(() => {
26+
return () => {
27+
resetTag();
28+
resetSelectedCategory();
29+
};
30+
}, [resetSelectedCategory, resetTag]);
31+
32+
const handleSave = () => {
33+
const categoryMap: Record<string, string[]> = {};
34+
35+
selectedTags.forEach(code => {
36+
const [categoryCode, keywordCode] = code.split(":");
37+
if (!categoryCode || !keywordCode) return;
38+
39+
if (!categoryMap[categoryCode]) {
40+
categoryMap[categoryCode] = [];
41+
}
42+
43+
categoryMap[categoryCode].push(keywordCode);
44+
});
45+
46+
handleSaveInterest.mutate({
47+
interests: Object.entries(categoryMap).map(([category, keywords]) => ({
48+
category,
49+
keywords,
50+
})),
51+
});
52+
};
53+
54+
return {
55+
modalSaving: handleSaveInterest.isPending,
56+
handleSave,
57+
};
58+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { INTERESTS_MOCK } from "@/features/mypage/consts/interests";
2+
import { useMemo, useRef } from "react";
3+
import { useEditInterestCategoryStore } from "../model/useEditInterestCategoryStore";
4+
import { useEditTagStore } from "../model/useEditTagStore";
5+
import { TagItem } from "./TagItem";
6+
7+
export const EditAsidePage = () => {
8+
const scrollRef = useRef<HTMLDivElement>(null);
9+
const selectedTags = useEditTagStore(state => state.selectedTags);
10+
const selectedCategory = useEditInterestCategoryStore(
11+
state => state.selectedCategory,
12+
);
13+
const setSelectedCategory = useEditInterestCategoryStore(
14+
state => state.setSelectedCategory,
15+
);
16+
const myInterestMap = useMemo(
17+
() =>
18+
selectedTags.reduce<Record<string, number>>((acc, code) => {
19+
const [categoryCode] = code.split(":");
20+
if (!categoryCode) return acc;
21+
acc[categoryCode] = (acc[categoryCode] ?? 0) + 1;
22+
return acc;
23+
}, {}),
24+
[selectedTags],
25+
);
26+
27+
const scrollToTop = () => {
28+
scrollRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
29+
};
30+
31+
return (
32+
<aside
33+
ref={scrollRef}
34+
className="w-75 border-x border-b border-bgNormal bg-sub-400 px-6 text-[#8B95A1]"
35+
>
36+
<h6 className="py-4 body-r-14">카테고리</h6>
37+
38+
<ul className="scrollbar-style flex h-150 w-65 flex-col overflow-scroll overflow-x-hidden">
39+
{INTERESTS_MOCK.interests.map(item => {
40+
const count = myInterestMap[item.code] ?? 0;
41+
42+
return (
43+
<TagItem
44+
key={item.code}
45+
tag={item.label}
46+
length={count}
47+
selected={item.code === selectedCategory}
48+
onClick={() => {
49+
setSelectedCategory(item.code);
50+
scrollToTop();
51+
}}
52+
/>
53+
);
54+
})}
55+
</ul>
56+
</aside>
57+
);
58+
};

0 commit comments

Comments
 (0)