Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"date-fns": "^4.1.0",
"dayjs": "^1.11.18",
"embla-carousel-react": "^8.6.0",
"framer-motion": "^12.27.1",
"googleapis": "^164.1.0",
"lucide-react": "^0.475.0",
"next": "15.2.8",
Expand Down Expand Up @@ -106,8 +107,7 @@
"typescript-eslint": "^8.24.0",
"vitest": "^3.1.1"
},
"resolutions": {
"resolutions": {
"strip-ansi": "6.0.1"
}

}
2 changes: 1 addition & 1 deletion src/app/(service)/(my)/notification/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';

import NotificationIcon from 'public/images/notification.svg';
import { useState } from 'react';

import type { GetMemberNotificationsTopicTypeEnum } from '@/api/openapi/api/notification-api';
Expand All @@ -13,6 +12,7 @@ import {
useGetNotificationCategories,
useReadNotifications,
} from '@/hooks/queries/notification-api';
import NotificationIcon from 'public/images/notification.svg';

const READ_STATUS_OPTIONS = [
{ value: 'all', label: '상태 전체' },
Expand Down
45 changes: 45 additions & 0 deletions src/app/(service)/home/home-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Suspense } from 'react';
import TabNavigation from '@/components/home/tab-navigation';
import ArchiveTab from '@/features/study/one-to-one/archive/ui/archive-tab';
import CommunityTab from '@/features/study/one-to-one/balance-game/ui/community-tab';
import HallOfFameTab from '@/features/study/one-to-one/hall-of-fame/ui/hall-of-fame-tab';
import StudyHistoryTab from '@/features/study/one-to-one/history/ui/study-history-tab';
import StudyTab from '@/features/study/one-to-one/schedule/ui/home-study-tab';

interface HomeContentProps {
activeTab: string;
}

export default function HomeContent({ activeTab }: HomeContentProps) {
const renderTabContent = () => {
switch (activeTab) {
case 'study':
return <StudyTab />;
case 'history':
return <StudyHistoryTab />;
case 'ranking':
return <HallOfFameTab />;
case 'archive':
return <ArchiveTab />;
case 'community':
return <CommunityTab />;
default:
return <StudyTab />;
}
};

return (
<>
<TabNavigation activeTab={activeTab} />
<Suspense
fallback={
<div className="text-text-subtlest py-800 text-center">
로딩 중...
</div>
}
>
{renderTabContent()}
</Suspense>
</>
);
}
23 changes: 14 additions & 9 deletions src/app/(service)/home/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Metadata } from 'next';
import StartStudyButton from '@/components/home/start-study-button';
import StudyCard from '@/features/study/schedule/ui/study-card';
import { generateMetadata as generateSEOMetadata } from '@/utils/seo';
import Banner from '@/widgets/home/banner';
import FeedbackLink from '@/widgets/home/feedback-link';
import HomeContent from './home-content';

export const metadata: Metadata = generateSEOMetadata({
title: '홈 - ZERO-ONE',
Expand All @@ -14,15 +14,20 @@ export const metadata: Metadata = generateSEOMetadata({
canonicalUrl: 'https://www.zeroone.it.kr/home',
});

export default async function Home() {
export default async function Home({
searchParams,
}: {
searchParams?: Promise<{ tab?: string }>;
}) {
const resolvedSearchParams = await searchParams;
const activeTab = resolvedSearchParams?.tab || 'study';

return (
<div className="mx-auto w-[1280px] px-400 py-600">
<div className="flex flex-col gap-500">
<Banner />
<FeedbackLink />
<StartStudyButton />
<StudyCard />
</div>
<div className="mx-auto flex w-[1496px] flex-col gap-500 px-600 py-600">
<Banner />
<FeedbackLink />
<StartStudyButton />
<HomeContent activeTab={activeTab} />
</div>
);
}
11 changes: 11 additions & 0 deletions src/app/(service)/insights/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ export default async function BlogPage({ searchParams }: BlogPageProps) {
{category.name}
</Link>
))}
<Link
href="/insights/weekly"
className="font-designer-15r relative px-300 pb-200 text-[#535862] transition-colors hover:text-[#6366f1]"
>
<span className="flex items-center gap-100">
위클리
<span className="animate-pulse rounded-full bg-gradient-to-r from-[#6366f1] to-[#8b5cf6] px-100 py-25 text-[10px] font-bold text-white">
NEW
</span>
</span>
</Link>
</div>

{/* 아티클 목록 */}
Expand Down
12 changes: 12 additions & 0 deletions src/app/(service)/insights/weekly/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import VotingDetailPageClient from '@/features/study/one-to-one/balance-game/ui/voting-detail-page-client';

export default async function VotingDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const votingId = Number(id);

return <VotingDetailPageClient votingId={votingId} />;
}
5 changes: 5 additions & 0 deletions src/app/(service)/insights/weekly/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import BalanceGamePage from '@/features/study/one-to-one/balance-game/ui/balance-game-page';

export default function VotingPage() {
return <BalanceGamePage />;
}
4 changes: 2 additions & 2 deletions src/app/(service)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ export default function ServiceLayout({
<body className={clsx(pretendard.className, 'h-screen w-screen')}>
<MainProvider>
<PageViewTracker />
<div className="w-full overflow-auto">
<div className="flex h-screen w-full flex-col overflow-hidden">
<Header />
<main className="h-[calc(100vh-62px)] w-full">{children}</main>
<main className="w-full flex-1 overflow-auto">{children}</main>
</div>
</MainProvider>
</body>
Expand Down
5 changes: 5 additions & 0 deletions src/app/(service)/one-on-one/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import OneOnOnePage from '@/features/study/one-to-one/ui/one-on-one-page';

export default function OneOnOnePageRoute() {
return <OneOnOnePage />;
}
135 changes: 135 additions & 0 deletions src/components/card/discussion-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { formatDistanceToNow } from 'date-fns';
import { ko } from 'date-fns/locale';
import { MessageCircle, ThumbsUp, ThumbsDown, Eye, Clock } from 'lucide-react';
import React from 'react';
import { cn } from '@/components/ui/(shadcn)/lib/utils';
import UserAvatar from '@/components/ui/avatar';
import UserProfileModal from '@/entities/user/ui/user-profile-modal';
import { TOPIC_LABELS } from '@/mocks/discussion-mock-data';
import { Discussion } from '@/types/discussion';

interface DiscussionCardProps {
discussion: Discussion;
onClick?: () => void;
}

export default function DiscussionCard({
discussion,
onClick,
}: DiscussionCardProps) {
const timeAgo = formatDistanceToNow(new Date(discussion.lastActivityAt), {
addSuffix: true,
locale: ko,
});

return (
<div
onClick={onClick}
className={cn(
'group rounded-200 bg-background-default ring-border-subtle cursor-pointer p-400 ring-1 ring-inset',
'hover:ring-border-brand transition-shadow duration-150',
)}
>
{/* 헤더: 작성자 정보 & 주제 */}
<div className="mb-200 flex items-center justify-between">
<div className="flex items-center gap-200">
{/* 아바타 & 닉네임 */}
<div onClick={(e) => e.stopPropagation()}>
<UserProfileModal
memberId={discussion.author.id}
trigger={
<div className="hover:ring-fill-brand-default-default flex cursor-pointer items-center gap-200 rounded-full px-200 py-100 ring-1 ring-transparent transition-shadow duration-100 ring-inset">
<div>
<UserAvatar
size={32}
image={discussion.author.avatar}
className="relative z-10"
/>
</div>
<span className="font-designer-13b text-text-default">
{discussion.author.nickname}
</span>
</div>
}
/>
</div>

{/* 시간 */}
<div className="flex items-center gap-100">
<span className="bg-border-subtle h-[10px] w-[1px]" />
<div className="font-designer-12r text-text-subtlest flex items-center gap-50">
<Clock className="h-3 w-3" />
{timeAgo}
</div>
</div>
</div>

{/* 주제 배지 */}
<div
className={cn(
'rounded-100 font-designer-12b px-200 py-50',
discussion.topic === 'development' && 'bg-blue-50 text-blue-600',
discussion.topic === 'study' && 'bg-green-50 text-green-600',
discussion.topic === 'free' && 'bg-purple-50 text-purple-600',
discussion.topic === 'question' && 'bg-orange-50 text-orange-600',
)}
>
{TOPIC_LABELS[discussion.topic]}
</div>
</div>

{/* 제목 */}
<h3 className="font-bold-h5 text-text-strong group-hover:text-text-brand mb-150 line-clamp-2 transition-colors">
{discussion.title}
</h3>

{/* 요약 */}
<p className="font-designer-14r text-text-subtle mb-300 line-clamp-2 leading-relaxed">
{discussion.summary}
</p>

{/* 태그 */}
{discussion.tags.length > 0 && (
<div className="mb-300 flex flex-wrap gap-100">
{discussion.tags.map((tag) => (
<span
key={tag}
className="rounded-100 bg-fill-neutral-subtle-default font-designer-12r text-text-subtle px-150 py-50"
>
#{tag}
</span>
))}
</div>
)}

{/* 하단 메타 정보 */}
<div className="border-border-subtlest flex items-center justify-between border-t pt-200">
<div className="flex items-center gap-300">
{/* 찬성 */}
<div className="font-designer-13m flex items-center gap-50 text-green-600">
<ThumbsUp className="h-4 w-4" />
<span>{discussion.vote.agreeCount}</span>
</div>

{/* 반대 */}
<div className="font-designer-13m flex items-center gap-50 text-red-500">
<ThumbsDown className="h-4 w-4" />
<span>{discussion.vote.disagreeCount}</span>
</div>

{/* 댓글 */}
<div className="font-designer-13m text-text-brand flex items-center gap-50">
<MessageCircle className="h-4 w-4" />
<span>{discussion.commentCount}</span>
</div>

{/* 조회수 */}
<div className="font-designer-13m text-text-subtle flex items-center gap-50">
<Eye className="h-4 w-4" />
<span>{discussion.viewCount.toLocaleString()}</span>
</div>
</div>
</div>
</div>
);
}
Loading
Loading