Skip to content

Commit f25c7da

Browse files
authored
Merge pull request #221 from DDD-Community/epic/home
Feat: Home 개편 및 API 연결
2 parents 7f77005 + 4f0ae81 commit f25c7da

File tree

108 files changed

+22367
-1958
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+22367
-1958
lines changed

package-lock.json

Lines changed: 15597 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"knip": "knip"
1313
},
1414
"dependencies": {
15+
"@hookform/resolvers": "^3.9.1",
1516
"@next/third-parties": "^15.5.6",
1617
"@radix-ui/react-dialog": "^1.1.15",
1718
"@radix-ui/react-dropdown-menu": "^2.1.15",
@@ -22,6 +23,8 @@
2223
"axios": "^1.10.0",
2324
"class-variance-authority": "^0.7.1",
2425
"clsx": "^2.1.1",
26+
"date-fns": "^4.1.0",
27+
"date-holidays": "^3.26.5",
2528
"lucide-react": "^0.519.0",
2629
"motion": "^12.19.1",
2730
"next": "15.3.8",
@@ -30,7 +33,8 @@
3033
"react-dom": "19.0.1",
3134
"react-hook-form": "^7.58.1",
3235
"swiper": "^12.0.2",
33-
"tailwind-merge": "^3.3.1"
36+
"tailwind-merge": "^3.3.1",
37+
"zod": "^3.24.0"
3438
},
3539
"devDependencies": {
3640
"@eslint/eslintrc": "^3",

public/mockServiceWorker.js

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
* - Please do NOT modify this file.
88
*/
99

10-
const PACKAGE_VERSION = '2.10.2'
11-
const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af'
10+
const PACKAGE_VERSION = '2.12.4'
11+
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
1212
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
1313
const activeClientIds = new Set()
1414

@@ -71,11 +71,6 @@ addEventListener('message', async function (event) {
7171
break
7272
}
7373

74-
case 'MOCK_DEACTIVATE': {
75-
activeClientIds.delete(clientId)
76-
break
77-
}
78-
7974
case 'CLIENT_CLOSED': {
8075
activeClientIds.delete(clientId)
8176

@@ -94,6 +89,8 @@ addEventListener('message', async function (event) {
9489
})
9590

9691
addEventListener('fetch', function (event) {
92+
const requestInterceptedAt = Date.now()
93+
9794
// Bypass navigation requests.
9895
if (event.request.mode === 'navigate') {
9996
return
@@ -110,23 +107,29 @@ addEventListener('fetch', function (event) {
110107

111108
// Bypass all requests when there are no active clients.
112109
// Prevents the self-unregistered worked from handling requests
113-
// after it's been deleted (still remains active until the next reload).
110+
// after it's been terminated (still remains active until the next reload).
114111
if (activeClientIds.size === 0) {
115112
return
116113
}
117114

118115
const requestId = crypto.randomUUID()
119-
event.respondWith(handleRequest(event, requestId))
116+
event.respondWith(handleRequest(event, requestId, requestInterceptedAt))
120117
})
121118

122119
/**
123120
* @param {FetchEvent} event
124121
* @param {string} requestId
122+
* @param {number} requestInterceptedAt
125123
*/
126-
async function handleRequest(event, requestId) {
124+
async function handleRequest(event, requestId, requestInterceptedAt) {
127125
const client = await resolveMainClient(event)
128126
const requestCloneForEvents = event.request.clone()
129-
const response = await getResponse(event, client, requestId)
127+
const response = await getResponse(
128+
event,
129+
client,
130+
requestId,
131+
requestInterceptedAt,
132+
)
130133

131134
// Send back the response clone for the "response:*" life-cycle events.
132135
// Ensure MSW is active and ready to handle the message, otherwise
@@ -202,9 +205,10 @@ async function resolveMainClient(event) {
202205
* @param {FetchEvent} event
203206
* @param {Client | undefined} client
204207
* @param {string} requestId
208+
* @param {number} requestInterceptedAt
205209
* @returns {Promise<Response>}
206210
*/
207-
async function getResponse(event, client, requestId) {
211+
async function getResponse(event, client, requestId, requestInterceptedAt) {
208212
// Clone the request because it might've been already used
209213
// (i.e. its body has been read and sent to the client).
210214
const requestClone = event.request.clone()
@@ -255,6 +259,7 @@ async function getResponse(event, client, requestId) {
255259
type: 'REQUEST',
256260
payload: {
257261
id: requestId,
262+
interceptedAt: requestInterceptedAt,
258263
...serializedRequest,
259264
},
260265
},

src/app/(home)/home/page.tsx

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
'use client';
22

3-
import { CheerMessageCard, GoalBanner } from '@/composite/home';
3+
import { TodoListContainer } from '@/composite/home';
44
import { GoalProvider } from '@/model/goal/context';
55
import { PlanProvider } from '@/model/todo/planSelector';
6-
import { WeeklyPlanBoard } from '@/composite/home/planBoard';
76
import { TodoListProvider } from '@/model/todo/todoList';
87
import { SelectedDayProvider } from '@/model/todo/selectedDay';
98
import { AIMentorProvider } from '@/model/aiMentor/context';
10-
import { Z_INDEX } from '@/shared/lib/z-index';
119

1210
export default function MainPage() {
1311
return (
@@ -16,24 +14,7 @@ export default function MainPage() {
1614
<TodoListProvider>
1715
<SelectedDayProvider>
1816
<AIMentorProvider>
19-
<div className="relative w-full">
20-
<CheerMessageCard type="grorong" />
21-
<div
22-
className={`absolute top-[140px] left-0 right-0 max-w-sm:mx-[20px] sm:mx-[40px] mx-auto bg-normal rounded-t-3xl shadow-xl ${Z_INDEX.CONTENT}`}
23-
>
24-
<div className="flex flex-col h-[calc(100vh-140px)]">
25-
<div className="flex flex-col flex-1 gap-6">
26-
<GoalBanner />
27-
<div className="px-4 md:px-0">
28-
<WeeklyPlanBoard />
29-
</div>
30-
</div>
31-
<div className="w-full px-4 mt-[32px] pb-[calc(100px+env(safe-area-inset-bottom))]">
32-
<CheerMessageCard type="aiMentor" />
33-
</div>
34-
</div>
35-
</div>
36-
</div>
17+
<TodoListContainer />
3718
</AIMentorProvider>
3819
</SelectedDayProvider>
3920
</TodoListProvider>

src/app/(home)/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function HomePageLayout({ children }: HomeLayoutProps) {
1414
return (
1515
<div className="flex flex-1 max-sm:flex-col max-w-md w-full mx-auto h-full">
1616
<NavigationBar />
17-
<NotifyOnboardModal />
17+
{/* <NotifyOnboardModal /> */}
1818
<div className="flex flex-1 flex-col overflow-x-hidden">{children}</div>
1919
</div>
2020
);

src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function RootLayout({
3737
return (
3838
<html lang="en">
3939
<GoogleTagManager gtmId="GTM-W8GCRPWX" />
40-
<body className={`${pretendard.variable} flex h-[100dvh] font-pretendard pretendard bg-[#1B1C1E]`}>
40+
<body className={`${pretendard.variable} flex h-[100dvh] font-pretendard pretendard bg-[#0F0F10]`}>
4141
<MSWClientProvider>
4242
<TanstackQueryWrapper>
4343
<ToastProvider>{children}</ToastProvider>

src/composite/goal/planetSelector/PlanetSelector.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import { Goal } from '@/shared/type/goal';
1515
import { BottomSheet, useBottomSheet } from '@/shared/components/feedBack/BottomSheet';
1616
import CreateNewGoal from './components/CreateNewGoal';
1717
import Button from '@/shared/components/input/Button';
18-
import GoalProgressSheet from '../goalProgressSheet';
1918
import { Swiper as SwiperType } from 'swiper/types';
2019
import { useShowEndedGoalsSheet } from './hooks';
2120
import { getMsUntilEndOfDay } from '@/shared/lib/utils';
@@ -24,9 +23,7 @@ export default function PlanetSelectorScene() {
2423
return (
2524
<GoalProvider goalListOption={{ year: 2025 }}>
2625
<PlanetSelector />
27-
<section className="pb-16">
28-
<GoalProgressSheet />
29-
</section>
26+
<section className="pb-16">{/* <GoalProgressSheet /> */}</section>
3027
</GoalProvider>
3128
);
3229
}

src/composite/home/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { CheerMessageCard } from './cheerMessageCard/component';
21
export { GoalRoadMap } from './goalRoadMap/component';
32
export { ContributionGraph } from './contributionGraph/component';
43
export { GoalBanner } from './goalBanner/component';
4+
export { TodoListContainer } from './todoListContainer';
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use client';
2+
3+
import { GoalTodo } from '@/shared/type/GoalTodo';
4+
import { useBottomSheet } from '@/shared/components/feedBack/BottomSheet';
5+
import FloatingButton from '@/shared/components/input/FloatingButton';
6+
import { Z_INDEX } from '@/shared/lib/z-index';
7+
import { TodoList } from '@/feature/todo/todoList';
8+
import { TodoBottomSheet } from '@/feature/todo/todoBottomSheet';
9+
import { Calendar } from '@/feature/todo/calendar';
10+
import { CheerMessageCard } from './components/cheerMessageCard';
11+
import { AddGoalButton } from './components/addGoalButton';
12+
import { TodoListContainerFormProvider } from './form';
13+
import { convertToFormData, getEditingTodoDefault } from './helper';
14+
15+
export const TodoListContainer = () => {
16+
const addSheet = useBottomSheet();
17+
const editSheet = useBottomSheet();
18+
19+
return (
20+
<TodoListContainerFormProvider>
21+
{({ selectedDate, calendarView, editingTodo, setSelectedDate, setCalendarView, setEditingTodo }) => {
22+
const isMonthlyView = calendarView === 'monthly';
23+
24+
const handleEdit = (todo: GoalTodo) => {
25+
editSheet.showSheet();
26+
setEditingTodo(todo);
27+
};
28+
29+
const handleCloseEditSheet = () => {
30+
editSheet.closeSheet();
31+
setEditingTodo(getEditingTodoDefault());
32+
};
33+
34+
return (
35+
<div className="relative w-full">
36+
{!isMonthlyView && <CheerMessageCard type="grorong" />}
37+
38+
<div
39+
className={`absolute left-0 right-0 max-w-sm:mx-[20px] sm:mx-[40px] mx-auto bg-[#0F0F10] shadow-xl transition-all duration-300 ease-in-out ${Z_INDEX.CONTENT} ${
40+
isMonthlyView ? 'top-0 rounded-none' : 'top-[140px] rounded-t-3xl'
41+
}`}
42+
>
43+
<div className={`flex flex-col ${isMonthlyView ? 'h-screen' : 'h-[calc(100vh-100px)]'}`}>
44+
<div className="flex flex-col flex-1 gap-6">
45+
<div className="px-4 md:px-0 pt-[24px]">
46+
<Calendar
47+
view={calendarView}
48+
selectedDate={selectedDate}
49+
onDateSelect={setSelectedDate}
50+
onViewChange={setCalendarView}
51+
/>
52+
<TodoList selectedDate={selectedDate} onEdit={handleEdit} />
53+
<AddGoalButton selectedDate={selectedDate} />
54+
</div>
55+
</div>
56+
</div>
57+
</div>
58+
59+
<FloatingButton onClick={addSheet.showSheet} aria-label="투두 추가" />
60+
61+
{/* 추가용 TodoBottomSheet */}
62+
<TodoBottomSheet
63+
mode="add"
64+
isOpen={addSheet.isOpen}
65+
onOpen={addSheet.showSheet}
66+
onClose={addSheet.closeSheet}
67+
selectedDate={selectedDate}
68+
/>
69+
70+
{/* 편집용 TodoBottomSheet */}
71+
<TodoBottomSheet
72+
mode="edit"
73+
isOpen={editSheet.isOpen}
74+
onOpen={editSheet.showSheet}
75+
onClose={handleCloseEditSheet}
76+
selectedDate={new Date(editingTodo.date)}
77+
values={convertToFormData(editingTodo)}
78+
todoId={editingTodo.id}
79+
/>
80+
</div>
81+
);
82+
}}
83+
</TodoListContainerFormProvider>
84+
);
85+
};
86+
87+
export default TodoListContainer;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use client';
2+
3+
import { useCallback } from 'react';
4+
import { useRouter } from 'next/navigation';
5+
import { format } from 'date-fns';
6+
import Button from '@/shared/components/input/Button';
7+
import { PlusIcon } from '@/shared/constants/icons';
8+
import { ROUTES } from '@/shared/constants/routes';
9+
import { useTodosByDate } from '@/model/todo/todoList';
10+
11+
interface AddGoalButtonProps {
12+
selectedDate: Date;
13+
}
14+
15+
export const AddGoalButton = ({ selectedDate }: AddGoalButtonProps) => {
16+
const router = useRouter();
17+
const dateString = format(selectedDate, 'yyyy-MM-dd');
18+
const { data: todosData } = useTodosByDate({ date: dateString });
19+
20+
const todoCount = todosData?.length ?? 0;
21+
22+
const handleAddGoal = useCallback(() => {
23+
router.push(ROUTES.CREATE_GOAL);
24+
}, [router]);
25+
26+
// todo가 없으면 버튼 숨김
27+
if (todoCount === 0) {
28+
return null;
29+
}
30+
31+
return (
32+
<div className="flex justify-center mb-[120px]">
33+
<Button
34+
size="ml"
35+
variant="tertiary"
36+
layout="icon-left"
37+
text="목표 추가하기"
38+
icon={<PlusIcon />}
39+
onClick={handleAddGoal}
40+
className="w-auto"
41+
/>
42+
</div>
43+
);
44+
};
45+
46+
export default AddGoalButton;

0 commit comments

Comments
 (0)