Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a3dba73
🩹 chore: add main page layout #7
minzee09 Oct 3, 2025
0d27963
💄 UI: add svgs #7
minzee09 Oct 12, 2025
784525a
✨ feat: add CardBadge #7
minzee09 Oct 12, 2025
5bcfbd0
✨ feat: add Card component #7
minzee09 Oct 12, 2025
8feb7d2
✨ feat: add card list component #7
minzee09 Oct 12, 2025
d81278c
💄 UI: add main layout #7
minzee09 Oct 27, 2025
f0b83bc
💄 UI: add card components #7
minzee09 Oct 27, 2025
d877e9b
💄 UI: add tagList component #7
minzee09 Oct 27, 2025
aafdf07
🩹 chore: add mock images #7
minzee09 Oct 27, 2025
8f50cad
✨ feat: add main page #7
minzee09 Oct 27, 2025
da044e9
♻️ refactor: move search folder #7
minzee09 Oct 27, 2025
245739c
♻️ refactor: rename component #7
minzee09 Oct 28, 2025
6005f7b
♻️ refactor: 더미 데이터 페이지로부터 분리 #7
minzee09 Oct 28, 2025
f8f321b
♻️ refactor: move mock image directory #7
minzee09 Oct 28, 2025
1cc3ff9
💄 UI: add filterBar component #7
minzee09 Oct 28, 2025
636f0ce
✨ feat: add card sorting util #7
minzee09 Oct 28, 2025
9f410f1
✨ feat: add search page #7
minzee09 Oct 28, 2025
4cda2dc
💄 UI: add title header layout #7
minzee09 Oct 28, 2025
550b2fc
✨ feat: 꿀팁 목록 사용하는 페이지 추가 #7
minzee09 Dec 15, 2025
25f00c2
💄 UI: add notifications page #7
minzee09 Dec 15, 2025
dfafb00
🩹 chore: add mock images for card carousel #7
minzee09 Dec 15, 2025
ab8ae1f
📦 package: add motion #7
minzee09 Dec 15, 2025
8dc108f
💄 UI: add main page card animation feature #7
minzee09 Dec 15, 2025
fbed32f
🔧 config: 스토리북에 이미지 보이게 수정 #7
minzee09 Dec 15, 2025
253fd45
🔧 config: 스토리북에 폰트 보이게 수정 #7
minzee09 Dec 15, 2025
cc1fbe6
♻️ refactor: modify component path #7
minzee09 Dec 15, 2025
e91a8f3
✨ feat: 마우스로도 드래그 가능해서 수정 #7
minzee09 Dec 15, 2025
c3e0cce
📝 docs: add storybook for card & filter components #7
minzee09 Dec 15, 2025
189872a
🔥 remove: empty page #7
minzee09 Dec 15, 2025
179ba48
🔧 config: disable eslint in storybook configuration file
minzee09 Dec 15, 2025
17beb1f
🔧 config: 이미지 임포트 설정 추가
minzee09 Dec 15, 2025
4affca7
🔨 fix: add Suspense for build error
minzee09 Dec 15, 2025
f8ce7a4
Merge branch 'develop' into feat/#7-UI-main
minzee09 Jan 12, 2026
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
36 changes: 22 additions & 14 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { StorybookConfig } from '@storybook/nextjs';

const config: StorybookConfig = {
Expand All @@ -15,23 +16,30 @@ const config: StorybookConfig = {
},
staticDirs: ['../public'],
webpackFinal: async config => {
if (config.module?.rules) {
config.module = config.module || {};
config.module.rules = config.module.rules || [];
config.module = config.module || {};
config.module.rules = config.module.rules || [];
const rules = config.module.rules;

const imageRule = config.module.rules.find(rule =>
rule?.['test']?.test('.svg'),
);
if (imageRule) {
imageRule['exclude'] = /\.svg$/;
}

config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
// 기존 이미지/svg 처리 rule에서 svg 제외
const imageRule = rules.find((rule: any) => rule?.test?.test?.('.svg'));
if (imageRule) {
imageRule.exclude = /\.svg$/i;
}

// SVG 처리: 기본은 URL(= next/image에 넣을 수 있음)
rules.push({
test: /\.svg$/i,
oneOf: [
{
resourceQuery: /component/, // import Icon from './x.svg?component'
use: ['@svgr/webpack'],
},
{
type: 'asset/resource', // import url from './x.svg'
},
],
});

return config;
},
};
Expand Down
19 changes: 12 additions & 7 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import type { Preview } from '@storybook/nextjs-vite';
import '../src/app/globals.css';
import { pretendard } from '../src/lib/fonts/pretendard';

const preview: Preview = {
decorators: [
Story => {
// pretendard 폰트 적용
if (typeof document !== 'undefined') {
document.documentElement.classList.add(pretendard.className);
}

return Story();
},
],
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},

a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: 'todo',
},
a11y: { test: 'todo' },
},
};

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@hookform/resolvers": "^5.2.2",
"clsx": "^2.1.1",
"globals": "^16.4.0",
"motion": "^12.23.26",
"immer": "^10.1.3",
"next": "15.5.3",
"react": "19.1.0",
Expand Down
60 changes: 60 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/ex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import { pretendard } from '@/lib/fonts/pretendard';
import './globals.css';
import { Suspense } from 'react';

const geistSans = Geist({
variable: '--font-geist-sans',
Expand Down Expand Up @@ -35,7 +36,9 @@ export default function RootLayout({
pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)]
"
>
<div className="px-6">{children}</div>
<div className="px-6">
<Suspense fallback={null}>{children}</Suspense>
</div>
</div>
</body>
</html>
Expand Down
14 changes: 14 additions & 0 deletions src/app/main/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import MainHeaderLayout from '@/features/main/components/headers/MainHeaderLayout';

export default function MainLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section className="">
<MainHeaderLayout title="홈마스터" />
{children}
</section>
);
}
61 changes: 61 additions & 0 deletions src/app/main/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'use client';

import CardListHorizontal from '@/features/main/components/cards/CardListHorizontal';
import { useRouter } from 'next/navigation';
import MockImg1 from '@/assets/images/mocks/card1.png';
import MockImg2 from '@/assets/images/mocks/card2.png';
import MockImg3 from '@/assets/images/mocks/card3.png';
import { mockCards } from '@/features/main/data/mockCards';
import CardStack, {
CardStackItem,
} from '@/common/components/CardStack/CardStack';
import { useCallback } from 'react';

const DUMMY_CARDS: CardStackItem[] = [
{ id: '1', imageUrl: MockImg1.src },
{ id: '2', imageUrl: MockImg2.src },
{ id: '3', imageUrl: MockImg3.src },
];

export default function MainPage() {
const router = useRouter();

const handleCardClick = useCallback(
(card: CardStackItem) => {
router.push(`/tips/${card.id}`);
},
[router],
);

const handleTodayTipsBtn = () => {
router.push('/today-tips');
};

const handleMonthlyTipsBtn = () => {
router.push('/monthly-tips');
};

return (
<main className="">
<h1 className="mb-12 text-title2 text-gray-1000 whitespace-pre-line">
{`안녕하세요!${'\n'}오늘도 홈마스터에서 꿀팁을 얻어가세요:)`}
</h1>
<CardStack cards={DUMMY_CARDS} onCardClick={handleCardClick} />
<div className="flex flex-col">
<CardListHorizontal
items={mockCards.slice(0, 2)}
title="오늘의 꿀팁"
onClick={handleTodayTipsBtn}
showBadge={false}
/>
<div className="h-4" />
<CardListHorizontal
items={mockCards.slice(0, 2)}
title="이달의 TOP10"
onClick={handleMonthlyTipsBtn}
showBadge={false}
/>
</div>
</main>
);
}
24 changes: 24 additions & 0 deletions src/app/monthly-tips/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import BackIcon from '@/assets/svgs/arrow_backward.svg';
import { useRouter } from 'next/navigation';
import TitleHeader from '@/features/main/components/headers/TitleHeaderLayout';

export default function MonthlyTipsLayout({
children,
}: {
children: React.ReactNode;
}) {
const router = useRouter();
return (
<>
<TitleHeader
title="이달의 꿀팁"
leftIcon={BackIcon}
onIconClick={() => router.back()}
sticky
/>
<main className="">{children}</main>
</>
);
}
34 changes: 34 additions & 0 deletions src/app/monthly-tips/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client';

import { useMemo, useState } from 'react';
import { sortCards, type SortFilterType } from '@/lib/utils/sortCards';
import { mockCards } from '@/features/main/data/mockCards';
import FilterBar from '@/common/components/FilterBar/FilterBar';
import CardList from '@/common/components/CardList/CardList';

export default function MonthlyTipsPage() {
const [filter, setFilter] = useState<SortFilterType>('ALL');
const results = mockCards;
const sorted = useMemo(() => sortCards(results, filter), [results, filter]);

const filteredWithBadges = useMemo(() => {
if (filter === 'ALL') {
return sorted.map(card => ({ ...card, badges: [] }));
}
const target = filter.toLowerCase();
return sorted.map(card => ({
...card,
badges: card.badges?.filter(b => b.type === target),
}));
}, [sorted, filter]);

return (
<div className="flex flex-col gap-4">
<FilterBar
defaultValue="all"
onChange={v => setFilter(String(v).toUpperCase() as SortFilterType)}
/>
<CardList items={filteredWithBadges} showBadge={filter !== 'ALL'} />
</div>
);
}
24 changes: 24 additions & 0 deletions src/app/notifications/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client';

import BackIcon from '@/assets/svgs/arrow_backward.svg';
import { useRouter } from 'next/navigation';
import TitleHeader from '@/features/main/components/headers/TitleHeaderLayout';

export default function NotificationLayout({
children,
}: {
children: React.ReactNode;
}) {
const router = useRouter();
return (
<>
<TitleHeader
title="알림"
leftIcon={BackIcon}
onIconClick={() => router.back()}
sticky
/>
<main className="">{children}</main>
</>
);
}
20 changes: 20 additions & 0 deletions src/app/notifications/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import NotificationCard from '@/features/notifications/components/NotificationCard';

export default function NotificationsPage() {
return (
<div className="flex flex-col bg-white gap-4 mt-4">
<NotificationCard
message="애니님이 올린 게시물이 이 달의 꿀팁 TOP 100에 선정되었어요!"
timeLabel="1시간 전"
/>
<NotificationCard
message="알림님이 애니님의 꿀팁을 저장했어요!"
timeLabel="1일 전"
/>
<NotificationCard
message="애니님! HOME MASTER의 신규 회원이 되신 것을 환영합니다!"
timeLabel="7일 전"
/>
</div>
);
}
Loading
Loading