Skip to content
Merged
49 changes: 49 additions & 0 deletions apps/re-member/src/app/layout/CommunityLayout.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Header
left={<Title>커뮤니티</Title>}
className="absolute left-0 right-0 top-0 bg-white"
/>

<div className="pt-header-height scrollbar-hide gap-3xl pb-bottom-padding flex h-full w-full flex-col overflow-y-auto">
<Tabs>
<Tabs.Item
icon={<BsFillClipboardFill />}
label="공지"
href={ROUTE.COMMUNITY_NOTICE}
/>
<Tabs.Item
icon={<BsFillEmojiSmileFill />}
label="자유"
href={ROUTE.COMMUNITY_FREE}
/>
<Tabs.Item
icon={<BsFillQuestionCircleFill />}
label="질문"
href={ROUTE.COMMUNITY_QUESTION}
/>
<Tabs.Item
icon={<BsFillInfoSquareFill />}
label="정보"
href={ROUTE.COMMUNITY_INFORMATION}
/>
</Tabs>
<Outlet />
</div>
<BottomNavbar />
</>
);
}
15 changes: 14 additions & 1 deletion apps/re-member/src/app/route/config.tsx
Original file line number Diff line number Diff line change
@@ -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([
{
Expand All @@ -19,6 +26,12 @@ export const router = createBrowserRouter([
path: `${ROUTE.ACTIVITY}/:id`,
children: [{ index: true, element: <StudyDetailPage /> }],
},
{
path: ROUTE.COMMUNITY,
element: <CommunityLayout />,
children: [{ index: true, element: <CommunityPage /> }],
},
{ path: `${ROUTE.COMMUNITY}/:id`, element: <CommunityDetailPage /> },
],
},
]);
35 changes: 35 additions & 0 deletions apps/re-member/src/app/types/community.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
28 changes: 28 additions & 0 deletions apps/re-member/src/components/community/CommunityFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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<boolean>(true);

const onClick = () => {
setIsLatest(!isLatest);
};
return (
<div className="px-gutter gap-md flex items-center">
<div className="scrollbar-hide min-w-0 flex-1 overflow-x-auto">
{tab === CATEGORY.INFORMATION && <CommunityInformationTab />}
{tab === CATEGORY.QUESTION && <CommunityQuestionTab />}
</div>
<Button color="text" size="small" className="shrink-0" onClick={onClick}>
{isLatest ? "최신순" : "인기순"}
</Button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="gap-lg flex overflow-auto">
{FILTERS.map((filter) => (
<Button
key={filter}
color={activeFilter === filter ? "outlineActive" : "ghost"}
size="small"
onClick={() => setActiveFilter(filter)}
>
{filter}
</Button>
))}
</div>
);
}
52 changes: 52 additions & 0 deletions apps/re-member/src/components/community/CommunityPostItem.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className="border-b-gray-2 gap-sm px-gutter py-xl flex cursor-pointer flex-col border-b"
role="button"
onClick={() => navigate(`${ROUTE.COMMUNITY}/${id}`)}
>
<div className="gap-md flex items-center">
{chipLabel && <Chip>{chipLabel}</Chip>}
<span className="text-16-medium truncate text-black">{title}</span>
</div>
<div className="text-12-regular text-gray-4 flex items-center justify-between">
<div className="gap-xs flex items-center">
<span>{createdAt}</span>
<span>|</span>
<span>
{author}({generation})
</span>
</div>
<div className="gap-md flex items-center">
<div className="gap-xs flex items-center">
<IoHeartOutline className="size-4" />
<span>{likeCount}</span>
</div>
<div className="gap-xs flex items-center">
<IoChatbubbleOutline className="size-4" />
<span>{commentCount}</span>
</div>
</div>
</div>
</div>
);
}
71 changes: 71 additions & 0 deletions apps/re-member/src/components/community/CommunityPostList.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex flex-col">
{MOCK_POSTS.map((post) => (
<CommunityPostItem
key={post.postData.id}
postData={post.postData}
chipLabel={post.chipLabel}
/>
))}
</div>
);
}
Loading