Skip to content

Commit cd4c8f7

Browse files
committed
Merge branch 'dev' into feature/home-page
# Conflicts: # components/home/ProfileSection.tsx
2 parents 1c25172 + 7170ed2 commit cd4c8f7

33 files changed

Lines changed: 2427 additions & 168 deletions

app/(with-sidebar)/(home)/page.tsx

Lines changed: 27 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,24 @@ import ProfileSection from '@/components/home/ProfileSection';
77
import ButtonSection from '@/components/home/ButtonSection';
88

99
import { useEffect, useState } from 'react';
10+
import { useAuthUser } from '@/hooks/useAuthUser';
1011

1112
import {
1213
Todo,
1314
fetchTodos,
1415
AddTodo,
1516
toggleTodoStatus,
16-
} from '@/lib/home/todoService';
17-
import { getProfile, Profile } from '@/lib/home/profileService';
18-
19-
import { User, onAuthStateChanged } from 'firebase/auth';
20-
import { auth } from '@/lib/firebase';
17+
} from '@/services/home/todoService.service';
18+
import { getProfile, Profile } from '@/services/home/profileService.service';
2119

2220
const Page = () => {
21+
// 현재 사용자 정보
22+
const { user: currentUser, authLoading } = useAuthUser();
23+
2324
const [profile, setProfile] = useState<Profile | null>(null);
2425
const [todos, setTodos] = useState<Todo[]>([]);
25-
2626
const [error, setError] = useState<string | null>(null);
2727

28-
// 현재 사용자 정보
29-
const [currentUser, setCurrentUser] = useState<User | null>(null);
30-
3128
// 1. 할 일 목록 불러오기
3229
const loadTodos = async (uid: string) => {
3330
try {
@@ -41,25 +38,9 @@ const Page = () => {
4138
}
4239
};
4340

44-
// 2. 할 일 추가
45-
const handleAddTodo = async (text: string) => {
46-
if (!currentUser) return;
47-
48-
try {
49-
setError(null);
50-
51-
await AddTodo(currentUser.uid, text);
52-
await loadTodos(currentUser.uid);
53-
} catch (err) {
54-
console.error(err);
55-
setError('할 일을 추가하는 데 실패하였습니다');
56-
}
57-
};
58-
5941
// 3. 할 일 상태 토글
6042
const handleToggleTodo = async (id: string, currentStatus: boolean) => {
6143
if (!currentUser) return;
62-
6344
try {
6445
setError(null);
6546

@@ -71,35 +52,30 @@ const Page = () => {
7152
}
7253
};
7354

74-
// 사용자 로그인 상태 감지
75-
useEffect(() => {
76-
const unsubscribe = onAuthStateChanged(auth, (user) => {
77-
setCurrentUser(user);
78-
});
79-
80-
return () => unsubscribe();
81-
}, []);
82-
8355
// 사용자가 존재하면 데이터 불러옴
8456
useEffect(() => {
85-
if (currentUser) {
86-
const loadData = async () => {
87-
try {
88-
const userProfile = await getProfile(currentUser.uid);
89-
setProfile(userProfile);
90-
await loadTodos(currentUser.uid);
91-
} catch (err) {
92-
console.error(err);
93-
setError('프로필 정보를 불러오는 데 실패하였습니다');
94-
}
95-
};
96-
loadData();
97-
} else {
98-
setTodos([]);
99-
setProfile(null);
100-
}
57+
if (!currentUser) return;
58+
(async () => {
59+
try {
60+
const userProfile = await getProfile(currentUser.uid);
61+
setProfile(userProfile);
62+
await loadTodos(currentUser.uid);
63+
} catch (err) {
64+
console.error(err);
65+
setError('프로필 정보를 불러오는 데 실패하였습니다');
66+
}
67+
})();
10168
}, [currentUser]);
10269

70+
// 유저 정보 로딩 처리
71+
if (authLoading) {
72+
return (
73+
<div className="flex min-h-screen items-center justify-center">
74+
<p>로그인 확인 중...</p>
75+
</div>
76+
);
77+
}
78+
10379
// 유저 정보가 없을 시
10480
if (!currentUser) {
10581
return (
@@ -133,7 +109,7 @@ const Page = () => {
133109
/>
134110

135111
{/* 3. ButtonSection */}
136-
<ButtonSection onAddTodo={handleAddTodo} />
112+
<ButtonSection />
137113
</div>
138114
);
139115
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Editor from '@/components/write/Editor';
2+
3+
type Props = {
4+
params: Promise<{ tilId: string }>;
5+
};
6+
7+
const Page = async ({ params }: Props) => {
8+
const { tilId } = await params;
9+
return (
10+
<div className="bg-background flex min-h-screen flex-col gap-8 p-4">
11+
<Editor tilId={tilId} />
12+
</div>
13+
);
14+
};
15+
16+
export default Page;

app/(with-sidebar)/layout.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
'use client';
2+
13
import Sidebar from '@/components/common/Sidebar';
4+
import { useAuthGuard } from '@/hooks/useAuthGuard';
25

36
import '@uiw/react-md-editor/markdown-editor.css';
47
import '@uiw/react-markdown-preview/markdown.css';
@@ -8,6 +11,13 @@ export default function SidebarLayout({
811
}: {
912
children: React.ReactNode;
1013
}) {
14+
const { loading, isAuthed } = useAuthGuard({ redirectTo: '/login' });
15+
16+
if (loading) return <div>로딩중...</div>;
17+
if (!isAuthed) {
18+
return null;
19+
}
20+
1121
return (
1222
<div className="flex">
1323
<Sidebar />

app/(with-sidebar)/plans/page.tsx

Lines changed: 129 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,118 @@
1-
import FormField from '@/components/auth/FormField';
1+
'use client';
2+
3+
import { useState, useEffect } from 'react';
4+
import { User, onAuthStateChanged } from 'firebase/auth';
5+
import { auth } from '@/lib/firebase';
6+
import {
7+
addPlan,
8+
deletePlan,
9+
deletePlanItem,
10+
fetchPlans,
11+
Plan,
12+
} from '@/services/plans/planManageService.service';
13+
214
import PageHeader from '@/components/common/PageHeader';
315
import Card from '@/components/home/Card';
416
import AddPlanButton from '@/components/plans/AddPlanButton';
517
import PlanSection from '@/components/plans/PlanSection';
618
import SearchBar from '@/components/plans/SearchBar';
7-
19+
import InlineAddPlanForm from '@/components/plans/InlineAddPlanForm';
820
import { Target, Calendar, CheckCircle2 } from 'lucide-react';
921

10-
const sampleTasks = [
11-
{
12-
id: 1,
13-
text: 'useState, useEffect 기초',
14-
date: '2025-01-20',
15-
isChecked: true,
16-
},
17-
{
18-
id: 2,
19-
text: 'useContext, useReducer',
20-
date: '2025-01-22',
21-
isChecked: false,
22-
},
23-
{
24-
id: 3,
25-
text: 'Custom Hooks 만들기',
26-
date: '2025-01-22',
27-
isChecked: false,
28-
},
29-
];
30-
3122
const Page = () => {
23+
const [user, setUser] = useState<User | null>(null);
24+
const [plans, setPlans] = useState<Plan[]>([]);
25+
const [isAdding, setIsAdding] = useState(false);
26+
const [isLoading, setIsLoading] = useState(true);
27+
28+
// 사용자 인증 상태 리스너 및 초기 플랜 목록 로드
29+
useEffect(() => {
30+
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
31+
setUser(currentUser);
32+
33+
if (currentUser) {
34+
try {
35+
const fetchedPlans = await fetchPlans(currentUser.uid);
36+
setPlans(fetchedPlans);
37+
} catch (err) {
38+
console.error('플랜 목록 로딩 실패:', err);
39+
setPlans([]);
40+
}
41+
} else {
42+
setPlans([]);
43+
}
44+
setIsLoading(false);
45+
});
46+
return () => unsubscribe(); // 클린업
47+
}, []);
48+
49+
// 플랜 생성(추가) 핸들러
50+
const handleSavePlan = async (title: string, description: string) => {
51+
if (!user) {
52+
alert('로그인이 필요합니다.');
53+
return;
54+
}
55+
try {
56+
await addPlan(user.uid, title, description);
57+
58+
// 목록 새로고침
59+
const fetchedPlans = await fetchPlans(user.uid);
60+
setPlans(fetchedPlans);
61+
62+
setIsAdding(false); // 폼 닫기
63+
} catch (err) {
64+
console.error('플랜 추가 실패:', err);
65+
}
66+
};
67+
68+
// 플랜 추가 취소 핸들러
69+
const handleCancelAdd = () => {
70+
setIsAdding(false);
71+
};
72+
73+
// 플랜 삭제 핸들러
74+
const handleDeletePlan = async (planId: string, title: string) => {
75+
if (!user) {
76+
alert('로그인이 필요합니다.');
77+
return;
78+
}
79+
80+
if (
81+
confirm(
82+
`'${title}' 플랜을 정말 삭제하시겠습니까? 포함된 모든 할 일이 삭제됩니다.`
83+
)
84+
) {
85+
try {
86+
await deletePlan(user.uid, planId);
87+
88+
// 목록 새로고침
89+
const fetchedPlans = await fetchPlans(user.uid);
90+
setPlans(fetchedPlans);
91+
} catch (err) {
92+
console.error(err);
93+
}
94+
}
95+
};
96+
3297
return (
3398
<div className="bg-background min-h-screen p-11">
34-
{/* 1. 페이지 헤더 */}
3599
<PageHeader
36100
title="플랜"
37101
highlight="관리하기"
38-
description="학습 주제를 만들고 세부 과제를 관리하세요"
102+
description="학습 주제를 만들고 세부 과제를 관리해보세요"
39103
/>
40104

41-
{/* 2. 상단 통계 카드 (Grid) */}
105+
{/* 상단 통계 카드 (Grid) */}
42106
<div className="mb-4 grid grid-cols-1 gap-3.5 md:grid-cols-3">
43107
<Card className="flex items-center justify-between border-2 border-[#D5DCFB]">
44108
{/* 왼쪽: 텍스트 영역 */}
45109
<div className="flex flex-col gap-1">
46110
<span className="text-text-sub text-sm font-medium">
47111
전체 플랜 수
48112
</span>
49-
<span className="text-4xl font-bold text-[#4757D3]">1</span>
113+
<span className="text-4xl font-bold text-[#4757D3]">
114+
{plans.length}
115+
</span>
50116
</div>
51117

52118
{/* 오른쪽: 아이콘 영역 */}
@@ -84,24 +150,45 @@ const Page = () => {
84150
</Card>
85151
</div>
86152

87-
{/* 3. 검색 바 */}
153+
{/* 검색 바 */}
88154
<SearchBar />
89155

90-
{/* 4. 메인 플랜 목록 */}
91-
<section className="space-y-6">
92-
<PlanSection
93-
title="React Hooks 학습"
94-
description="React Hooks의 기본부터 고급 패턴까지 학습"
95-
tasks={sampleTasks}
96-
/>
97-
98-
{/* 추가적인 PlanSection이 있다면 여기에 배치 */}
99-
</section>
156+
{isLoading ? (
157+
<p>플랜을 불러오는 중...</p>
158+
) : (
159+
<div>
160+
<section className="space-y-6">
161+
{plans.length > 0 && user ? (
162+
plans.map((plan) => (
163+
<PlanSection
164+
key={plan.id}
165+
userId={user.uid}
166+
planId={plan.id}
167+
title={plan.title}
168+
description={plan.description}
169+
onDeletePlan={handleDeletePlan}
170+
/>
171+
))
172+
) : (
173+
<p>아직 생성된 플랜이 없습니다. 첫 플랜을 추가해보세요!</p>
174+
)}
175+
</section>
100176

101-
{/* 5. 하단 추가 버튼 */}
102-
<div className="mt-6">
103-
<AddPlanButton />
104-
</div>
177+
{/* 하단 추가 버튼 or 인라인 폼 */}
178+
<div className="mt-6">
179+
{isAdding ? (
180+
<InlineAddPlanForm
181+
onSave={handleSavePlan}
182+
onCancel={handleCancelAdd}
183+
/>
184+
) : (
185+
<div onClick={() => setIsAdding(true)}>
186+
<AddPlanButton />
187+
</div>
188+
)}
189+
</div>
190+
</div>
191+
)}
105192
</div>
106193
);
107194
};

0 commit comments

Comments
 (0)