From 5494cccf4a94491658283c3c5c1afac1f7351802 Mon Sep 17 00:00:00 2001 From: seoJing Date: Sat, 17 Jan 2026 19:16:58 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat(re-member):=20=EB=B2=84=ED=8A=BC=20tex?= =?UTF-8?q?t=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/re-member/src/components/common/button/button.css.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/re-member/src/components/common/button/button.css.ts b/apps/re-member/src/components/common/button/button.css.ts index 7b96d37d..708b3e20 100644 --- a/apps/re-member/src/components/common/button/button.css.ts +++ b/apps/re-member/src/components/common/button/button.css.ts @@ -17,6 +17,7 @@ export const buttonVariant = cva( outlineDisabled: "border border-gray-3 text-gray-3 bg-gray-1 active:brightness-100", ghost: "bg-transparent text-gray-3 ", + text: "bg-transparent text-black ", }, }, defaultVariants: { From 537bb64e4ef97112e0f31ceada35cc06e0aa3c74 Mon Sep 17 00:00:00 2001 From: seoJing Date: Sat, 17 Jan 2026 19:17:50 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat(re-member):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=EB=9D=BC=EC=9A=B0=ED=84=B0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/re-member/src/app/route/config.tsx | 15 ++++++++++++++- apps/re-member/src/pages/index.ts | 2 ++ apps/re-member/src/shared/config/route.ts | 4 ++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/re-member/src/app/route/config.tsx b/apps/re-member/src/app/route/config.tsx index 9c00513c..9b154393 100644 --- a/apps/re-member/src/app/route/config.tsx +++ b/apps/re-member/src/app/route/config.tsx @@ -1,8 +1,15 @@ import { createBrowserRouter } from "react-router"; import RootLayout from "../layout/RootLayout"; -import { ActivityHomePage, HomePage, StudyDetailPage } from "@/pages"; +import { + ActivityHomePage, + HomePage, + StudyDetailPage, + CommunityDetailPage, + CommunityPage, +} from "@/pages"; import { ROUTE } from "@/shared/config/route"; import ActivityLayout from "../layout/ActivityLayout"; +import CommunityLayout from "../layout/CommunityLayout"; export const router = createBrowserRouter([ { @@ -19,6 +26,12 @@ export const router = createBrowserRouter([ path: `${ROUTE.ACTIVITY}/:id`, children: [{ index: true, element: }], }, + { + path: ROUTE.COMMUNITY, + element: , + children: [{ index: true, element: }], + }, + { path: `${ROUTE.COMMUNITY}/:id`, element: }, ], }, ]); diff --git a/apps/re-member/src/pages/index.ts b/apps/re-member/src/pages/index.ts index a58e81cf..2c1d373d 100644 --- a/apps/re-member/src/pages/index.ts +++ b/apps/re-member/src/pages/index.ts @@ -2,3 +2,5 @@ export { default as HomePage } from "./home/HomePage"; export { default as ActivityHomePage } from "./activity/ActivityHomePage"; export { default as ActivityStudyPage } from "./activity/ActivityStudyPage"; export { default as StudyDetailPage } from "./activity/study/StudyDetailPage"; +export { default as CommunityPage } from "./community/CommunityPage"; +export { default as CommunityDetailPage } from "./community/CommunityDetailPage"; diff --git a/apps/re-member/src/shared/config/route.ts b/apps/re-member/src/shared/config/route.ts index a860939a..01c620b3 100644 --- a/apps/re-member/src/shared/config/route.ts +++ b/apps/re-member/src/shared/config/route.ts @@ -1,6 +1,10 @@ export const ROUTE = { HOME: "/", COMMUNITY: "/community", + COMMUNITY_NOTICE: "/community?tab=notice", + COMMUNITY_FREE: "/community?tab=free", + COMMUNITY_QUESTION: "/community?tab=question", + COMMUNITY_INFORMATION: "/community?tab=information", ACTIVITY: "/activity", ACTIVITY_STUDY: "/activity?tab=study", LIBRARY: "/library", From f7c2ce7c98cd5f588a53e6b5d92a6c1d77fdf1fd Mon Sep 17 00:00:00 2001 From: seoJing Date: Sat, 17 Jan 2026 19:19:56 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat(re-member):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/layout/CommunityLayout.tsx | 49 ++++++++ apps/re-member/src/app/types/community.ts | 35 ++++++ .../components/community/CommunityFilter.tsx | 26 ++++ .../community/CommunityInformationTab.tsx | 23 ++++ .../community/CommunityPostItem.tsx | 52 ++++++++ .../community/CommunityPostList.tsx | 71 +++++++++++ .../community/CommunityQuestionTab.tsx | 118 ++++++++++++++++++ .../community/CommunityWriteButton.tsx | 7 ++ .../src/components/community/index.ts | 6 + .../src/pages/community/CommunityPage.tsx | 23 ++++ 10 files changed, 410 insertions(+) create mode 100644 apps/re-member/src/app/layout/CommunityLayout.tsx create mode 100644 apps/re-member/src/app/types/community.ts create mode 100644 apps/re-member/src/components/community/CommunityFilter.tsx create mode 100644 apps/re-member/src/components/community/CommunityInformationTab.tsx create mode 100644 apps/re-member/src/components/community/CommunityPostItem.tsx create mode 100644 apps/re-member/src/components/community/CommunityPostList.tsx create mode 100644 apps/re-member/src/components/community/CommunityQuestionTab.tsx create mode 100644 apps/re-member/src/components/community/CommunityWriteButton.tsx create mode 100644 apps/re-member/src/components/community/index.ts create mode 100644 apps/re-member/src/pages/community/CommunityPage.tsx diff --git a/apps/re-member/src/app/layout/CommunityLayout.tsx b/apps/re-member/src/app/layout/CommunityLayout.tsx new file mode 100644 index 00000000..6f067b3f --- /dev/null +++ b/apps/re-member/src/app/layout/CommunityLayout.tsx @@ -0,0 +1,49 @@ +import { Header, Tabs, Title } from "@/components/common"; +import { BottomNavbar } from "@/components/menu"; + +import { ROUTE } from "@/shared/config/route"; +import { + BsFillClipboardFill, + BsFillEmojiSmileFill, + BsFillInfoSquareFill, + BsFillQuestionCircleFill, +} from "react-icons/bs"; +import { Outlet } from "react-router"; + +export default function CommunityLayout() { + return ( + <> +
커뮤니티} + className="absolute left-0 right-0 top-0 bg-white" + /> + +
+ + } + label="공지" + href={ROUTE.COMMUNITY_NOTICE} + /> + } + label="자유" + href={ROUTE.COMMUNITY_FREE} + /> + } + label="질문" + href={ROUTE.COMMUNITY_QUESTION} + /> + } + label="정보" + href={ROUTE.COMMUNITY_INFORMATION} + /> + + +
+ + + ); +} diff --git a/apps/re-member/src/app/types/community.ts b/apps/re-member/src/app/types/community.ts new file mode 100644 index 00000000..8ff87f83 --- /dev/null +++ b/apps/re-member/src/app/types/community.ts @@ -0,0 +1,35 @@ +export const CATEGORY = { + NOTICE: "notice", + FREE: "free", + QUESTION: "question", + INFORMATION: "information", +}; + +export interface PostData { + id: number; + title: string; + createdAt: string; + author: string; + generation: number; + likeCount: number; + commentCount: number; +} + +export interface PostDetailData { + author: string; + generation: number; + createdAt: string; + content: string; + likeCount: number; + commentCount: number; +} + +export interface PostDetailCommentData { + id: number; + author: string; + generation: number; + createdAt: string; + content: string; + likeCount: number; + isAuthor?: boolean; +} diff --git a/apps/re-member/src/components/community/CommunityFilter.tsx b/apps/re-member/src/components/community/CommunityFilter.tsx new file mode 100644 index 00000000..1dd9acaf --- /dev/null +++ b/apps/re-member/src/components/community/CommunityFilter.tsx @@ -0,0 +1,26 @@ +import { CATEGORY } from "@/app/types/community"; +import { Button } from "../common"; +import CommunityInformationTab from "./CommunityInformationTab"; +import CommunityQuestionTab from "./CommunityQuestionTab"; +import { useState } from "react"; + +interface CommunityFilterProps { + tab: string; +} + +export default function CommunityFilter({ tab }: CommunityFilterProps) { + const [isLatest, setIsLatest] = useState(true); + + const onClick = () => { + setIsLatest(!isLatest); + }; + return ( +
+ {tab === CATEGORY.INFORMATION && } + {tab === CATEGORY.QUESTION && } + +
+ ); +} diff --git a/apps/re-member/src/components/community/CommunityInformationTab.tsx b/apps/re-member/src/components/community/CommunityInformationTab.tsx new file mode 100644 index 00000000..758bb96b --- /dev/null +++ b/apps/re-member/src/components/community/CommunityInformationTab.tsx @@ -0,0 +1,23 @@ +import { useState } from "react"; +import { Button } from "../common"; + +const FILTERS: string[] = ["전체", "IT 소식", "채용 정보"]; + +export default function CommunityInformationTab() { + const [activeFilter, setActiveFilter] = useState(FILTERS[0]); + + return ( +
+ {FILTERS.map((filter) => ( + + ))} +
+ ); +} diff --git a/apps/re-member/src/components/community/CommunityPostItem.tsx b/apps/re-member/src/components/community/CommunityPostItem.tsx new file mode 100644 index 00000000..c22c3bf7 --- /dev/null +++ b/apps/re-member/src/components/community/CommunityPostItem.tsx @@ -0,0 +1,52 @@ +import { IoHeartOutline, IoChatbubbleOutline } from "react-icons/io5"; +import { useNavigate } from "react-router"; +import { Chip } from "../common"; +import { ROUTE } from "@/shared/config/route"; +import type { PostData } from "@/app/types/community"; + +interface CommunityPostItemProps { + postData: PostData; + chipLabel?: string; +} + +export default function CommunityPostItem({ + postData, + chipLabel, +}: CommunityPostItemProps) { + const { id, title, createdAt, author, generation, likeCount, commentCount } = + postData; + + const navigate = useNavigate(); + + return ( +
navigate(`${ROUTE.COMMUNITY}/${id}`)} + > +
+ {chipLabel && {chipLabel}} + {title} +
+
+
+ {createdAt} + | + + {author}({generation}) + +
+
+
+ + {likeCount} +
+
+ + {commentCount} +
+
+
+
+ ); +} diff --git a/apps/re-member/src/components/community/CommunityPostList.tsx b/apps/re-member/src/components/community/CommunityPostList.tsx new file mode 100644 index 00000000..09aff70b --- /dev/null +++ b/apps/re-member/src/components/community/CommunityPostList.tsx @@ -0,0 +1,71 @@ +import type { PostData } from "@/app/types/community"; +import CommunityPostItem from "./CommunityPostItem"; + +interface PostItem { + postData: PostData; + chipLabel?: string; +} + +const MOCK_POSTS: PostItem[] = [ + { + postData: { + id: 1, + title: "송년회 참석 투표에 참여해주세요!", + createdAt: "3분 전", + author: "한유진", + generation: 23, + likeCount: 12, + commentCount: 12, + }, + chipLabel: "공지", + }, + { + postData: { + id: 2, + title: "아자스입니다", + createdAt: "12분 전", + author: "한유진", + generation: 23, + likeCount: 12, + commentCount: 12, + }, + chipLabel: "공지", + }, + { + postData: { + id: 3, + title: "아자스입니다", + createdAt: "28분 전", + author: "한유진", + generation: 23, + likeCount: 12, + commentCount: 12, + }, + chipLabel: "공지", + }, + { + postData: { + id: 4, + title: "아자스입니다", + createdAt: "30분 전", + author: "한유진", + generation: 23, + likeCount: 12, + commentCount: 12, + }, + }, +]; + +export default function CommunityPostList() { + return ( +
+ {MOCK_POSTS.map((post) => ( + + ))} +
+ ); +} diff --git a/apps/re-member/src/components/community/CommunityQuestionTab.tsx b/apps/re-member/src/components/community/CommunityQuestionTab.tsx new file mode 100644 index 00000000..c85d2402 --- /dev/null +++ b/apps/re-member/src/components/community/CommunityQuestionTab.tsx @@ -0,0 +1,118 @@ +import { useState } from "react"; +import { IoChevronDown } from "react-icons/io5"; +import { Button, Dropdown } from "../common"; + +interface FilterOption { + label: string; + value: string; +} + +const LANGUAGE_OPTIONS: FilterOption[] = [ + { label: "언어", value: "all" }, + { label: "JavaScript", value: "javascript" }, + { label: "TypeScript", value: "typescript" }, + { label: "Python", value: "python" }, + { label: "Java", value: "java" }, + { label: "C/C++", value: "cpp" }, + { label: "Go", value: "go" }, +]; + +const FIELD_OPTIONS: FilterOption[] = [ + { label: "분야", value: "all" }, + { label: "프론트엔드", value: "frontend" }, + { label: "백엔드", value: "backend" }, + { label: "모바일", value: "mobile" }, + { label: "데브옵스", value: "devops" }, + { label: "데이터", value: "data" }, +]; + +const TECH_OPTIONS: FilterOption[] = [ + { label: "기술", value: "all" }, + { label: "React", value: "react" }, + { label: "Next.js", value: "nextjs" }, + { label: "Node.js", value: "nodejs" }, + { label: "Spring", value: "spring" }, + { label: "AWS", value: "aws" }, +]; + +export default function CommunityQuestionTab() { + const [language, setLanguage] = useState("all"); + const [field, setField] = useState("all"); + const [tech, setTech] = useState("all"); + + const getSelectedLabel = (options: FilterOption[], value: string) => { + return ( + options.find((opt) => opt.value === value)?.label ?? options[0].label + ); + }; + + return ( +
+ + {getSelectedLabel(LANGUAGE_OPTIONS, language)} + + + } + align="start" + > + {LANGUAGE_OPTIONS.map((option) => ( + setLanguage(option.value)} + > + {option.label} + + ))} + + + + {getSelectedLabel(FIELD_OPTIONS, field)} + + + } + align="start" + > + {FIELD_OPTIONS.map((option) => ( + setField(option.value)} + > + {option.label} + + ))} + + + + {getSelectedLabel(TECH_OPTIONS, tech)} + + + } + align="start" + > + {TECH_OPTIONS.map((option) => ( + setTech(option.value)} + > + {option.label} + + ))} + +
+ ); +} diff --git a/apps/re-member/src/components/community/CommunityWriteButton.tsx b/apps/re-member/src/components/community/CommunityWriteButton.tsx new file mode 100644 index 00000000..ff0e0e6e --- /dev/null +++ b/apps/re-member/src/components/community/CommunityWriteButton.tsx @@ -0,0 +1,7 @@ +export default function CommunityWriteButton() { + return ( + + ); +} diff --git a/apps/re-member/src/components/community/index.ts b/apps/re-member/src/components/community/index.ts new file mode 100644 index 00000000..1e8c6f83 --- /dev/null +++ b/apps/re-member/src/components/community/index.ts @@ -0,0 +1,6 @@ +export { default as CommunityPostList } from "./CommunityPostList"; +export { default as CommunityInformationTab } from "./CommunityInformationTab"; +export { default as CommunityQuestionTab } from "./CommunityQuestionTab"; +export { default as CommunityFilter } from "./CommunityFilter"; +export { default as CommunityWriteButton } from "./CommunityWriteButton"; +export * from "./communityPostDetail"; diff --git a/apps/re-member/src/pages/community/CommunityPage.tsx b/apps/re-member/src/pages/community/CommunityPage.tsx new file mode 100644 index 00000000..8d2364d2 --- /dev/null +++ b/apps/re-member/src/pages/community/CommunityPage.tsx @@ -0,0 +1,23 @@ +import { Section } from "@/components/common"; +import { + CommunityPostList, + CommunityFilter, + CommunityWriteButton, +} from "@/components/community"; +import { CATEGORY } from "@/app/types/community"; +import { useSearchParams } from "react-router"; + +export default function CommunityPage() { + const [searchParams] = useSearchParams(); + const tab = searchParams.get("tab"); + + return ( + <> +
+ + + +
+ + ); +} From 57470660f8aa425b5d809fcceff84d7d50bf6111 Mon Sep 17 00:00:00 2001 From: seoJing Date: Sat, 17 Jan 2026 19:20:18 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat(re-member):=20=EC=BB=A4=EB=AE=A4?= =?UTF-8?q?=EB=8B=88=ED=8B=B0=20=EB=94=94=ED=85=8C=EC=9D=BC=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PostDetailCommentInput.tsx | 42 +++++++++++ .../PostDetailCommentItem.tsx | 68 +++++++++++++++++ .../PostDetailCommentSection.tsx | 26 +++++++ .../communityPostDetail/PostDetailContent.tsx | 57 ++++++++++++++ .../community/communityPostDetail/index.ts | 4 + .../pages/community/CommunityDetailPage.tsx | 74 +++++++++++++++++++ 6 files changed, 271 insertions(+) create mode 100644 apps/re-member/src/components/community/communityPostDetail/PostDetailCommentInput.tsx create mode 100644 apps/re-member/src/components/community/communityPostDetail/PostDetailCommentItem.tsx create mode 100644 apps/re-member/src/components/community/communityPostDetail/PostDetailCommentSection.tsx create mode 100644 apps/re-member/src/components/community/communityPostDetail/PostDetailContent.tsx create mode 100644 apps/re-member/src/components/community/communityPostDetail/index.ts create mode 100644 apps/re-member/src/pages/community/CommunityDetailPage.tsx diff --git a/apps/re-member/src/components/community/communityPostDetail/PostDetailCommentInput.tsx b/apps/re-member/src/components/community/communityPostDetail/PostDetailCommentInput.tsx new file mode 100644 index 00000000..1f0a4117 --- /dev/null +++ b/apps/re-member/src/components/community/communityPostDetail/PostDetailCommentInput.tsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import { IoSend } from "react-icons/io5"; + +interface PostDetailCommentInputProps { + onSubmit?: (comment: string) => void; +} + +export default function PostDetailCommentInput({ + onSubmit, +}: PostDetailCommentInputProps) { + const [comment, setComment] = useState(""); + + const handleSubmit = () => { + if (comment.trim() && onSubmit) { + onSubmit(comment); + setComment(""); + } + }; + + return ( +
+
+
+