Skip to content

Commit e407179

Browse files
authored
Merge branch 'dev' into feature/landing-page
2 parents 9a76563 + b3bf487 commit e407179

14 files changed

Lines changed: 389 additions & 8 deletions

File tree

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
1-
// export default function Home() {
2-
// return <div>home</div>;
3-
// }
1+
import BottomSection from '@/components/home/BottomSection';
2+
import GraphSection from '@/components/home/GraphSection';
3+
import HeaderSection from '@/components/home/HeaderSection';
4+
import ProfileSection from '@/components/home/ProfileSection';
5+
import ButtonSection from '@/components/home/ButtonSection';
46

57
const Page = () => {
6-
return <div>home</div>;
8+
return (
9+
<div className="bg-background flex min-h-screen flex-col gap-4 font-sans md:p-[137px]">
10+
{/* 1. Header */}
11+
<HeaderSection />
12+
13+
{/* 2-1. ProfileSection */}
14+
<ProfileSection className="grid grid-cols-1 gap-4 md:grid-cols-3" />
15+
16+
{/* 2-2. GraphSection */}
17+
<GraphSection></GraphSection>
18+
19+
{/* 2-3. BottomSection */}
20+
<BottomSection className="grid grid-cols-1 gap-4 md:grid-cols-2" />
21+
22+
{/* 3. ButtonSection */}
23+
<ButtonSection />
24+
</div>
25+
);
726
};
827

928
export default Page;

app/(with-sidebar)/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function SidebarLayout({
88
return (
99
<div className="flex">
1010
<Sidebar />
11-
<main className="bg-background flex-1">{children}</main>
11+
<main className="bg-background flex-1 pl-48">{children}</main>
1212
</div>
1313
);
1414
}

components/auth/LoginForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const LoginForm = ({ handleOpenModal }: LoginFormProps) => {
3636
);
3737
const user = result.user;
3838
// console.log('로그인 성공 : ', user);
39-
router.push('/main');
39+
router.push('/');
4040
} catch (err: unknown) {
4141
if (err instanceof Error) {
4242
}
@@ -65,7 +65,7 @@ const LoginForm = ({ handleOpenModal }: LoginFormProps) => {
6565
password
6666
);
6767
// console.log('로그인 성공!', userCredential.user);
68-
router.push('/main');
68+
router.push('/');
6969
} catch (err) {
7070
// console.error('로그인 에러:', err);
7171
setError('이메일 또는 비밀번호가 잘못되었습니다.');

components/common/Sidebar.tsx

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,86 @@
1+
'use client';
2+
3+
import Link from 'next/link';
4+
import { usePathname, useRouter } from 'next/navigation';
5+
import {
6+
Home,
7+
ClipboardList,
8+
CalendarPlus,
9+
CopyPlus,
10+
LogOut,
11+
} from 'lucide-react';
12+
import { signOut } from 'firebase/auth';
13+
import { auth } from '@/lib/firebase';
14+
15+
const navItems = [
16+
{ label: '홈', href: '/', icon: Home },
17+
{ label: '개발 일지', href: '/logs', icon: ClipboardList },
18+
{ label: '새 계획 만들기', href: '/plans/new', icon: CalendarPlus },
19+
{ label: '새 페이지 만들기', href: '/pages/new', icon: CopyPlus },
20+
];
21+
122
const Sidebar = () => {
2-
return <div className="w-48 bg-white"></div>;
23+
const pathname = usePathname();
24+
const router = useRouter();
25+
26+
const handleLogout = async (): Promise<void> => {
27+
try {
28+
await signOut(auth);
29+
router.replace('/landing');
30+
router.refresh(); // 서버 컴포넌트/세션 UI 갱신
31+
} catch (err) {
32+
console.error('로그아웃 실패:', err);
33+
alert('로그아웃에 실패했습니다.');
34+
}
35+
};
36+
37+
return (
38+
<div className="fixed flex h-screen w-48 flex-col bg-white">
39+
<div className="flex items-center gap-2 px-6 py-5">
40+
<span className="text-primary text-lg font-bold">&lt;/&gt;</span>
41+
<span>DevFlow</span>
42+
</div>
43+
<hr className="border-slate-300" />
44+
<nav className="mt-4 px-4">
45+
<ul className="space-y-2">
46+
{navItems.map(({ label, href, icon: Icon }) => {
47+
const isActive = pathname === href;
48+
49+
return (
50+
<li key={href}>
51+
<Link
52+
href={href}
53+
className={[
54+
'flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium transition',
55+
isActive
56+
? 'bg-slate-200 text-slate-900'
57+
: 'text-slate-700 hover:bg-slate-100',
58+
].join(' ')}
59+
>
60+
<Icon
61+
className={[
62+
'h-5 w-5',
63+
isActive ? 'text-slate-900' : 'text-slate-600',
64+
].join(' ')}
65+
/>
66+
{label}
67+
</Link>
68+
</li>
69+
);
70+
})}
71+
</ul>
72+
</nav>
73+
<div className="absolute bottom-0 w-full border-t border-slate-200 p-4">
74+
<button
75+
className="flex items-center gap-3 rounded-lg px-4 py-3 text-sm font-medium text-slate-700 hover:bg-slate-100"
76+
onClick={handleLogout}
77+
>
78+
<LogOut className="h-5 w-5 text-slate-600" />
79+
로그아웃
80+
</button>
81+
</div>
82+
</div>
83+
);
384
};
485

586
export default Sidebar;

components/home/BottomSection.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import Card from './Card';
5+
import CheckList, { ChecklistItem } from './CheckList';
6+
7+
interface BottomSectionProps {
8+
className?: string;
9+
}
10+
11+
const TODAY_DUMMY = [
12+
{ id: 't1', text: 'React Hooks 정리', isChecked: false },
13+
{ id: 't2', text: 'GSAP ScrollTrigger 복습', isChecked: true },
14+
{ id: 't3', text: '알고리즘 1문제 풀기', isChecked: false },
15+
];
16+
17+
const UPCOMING_DUMMY = [
18+
{ id: 'u1', text: 'Next.js App Router 정리', isChecked: false },
19+
{ id: 'u2', text: '포트폴리오 리팩토링', isChecked: false },
20+
];
21+
22+
export default function BottomSection({ className }: BottomSectionProps) {
23+
const [today, setToday] = useState<ChecklistItem[]>(TODAY_DUMMY);
24+
const [upcoming, setUpcoming] = useState<ChecklistItem[]>(UPCOMING_DUMMY);
25+
26+
const toggleToday = (id: string) => {
27+
setToday((prev) =>
28+
prev.map((it) =>
29+
it.id === id ? { ...it, isChecked: !it.isChecked } : it
30+
)
31+
);
32+
};
33+
34+
const toggleUpcoming = (id: string) => {
35+
setUpcoming((prev) =>
36+
prev.map((it) =>
37+
it.id === id ? { ...it, isChecked: !it.isChecked } : it
38+
)
39+
);
40+
};
41+
42+
return (
43+
<div className={className}>
44+
<Card title="오늘 할 일">
45+
<CheckList
46+
items={today}
47+
onToggle={toggleToday}
48+
emptyText="오늘 할 일이 없습니다"
49+
/>
50+
</Card>
51+
52+
<Card title="다가오는 일정">
53+
<CheckList
54+
items={upcoming}
55+
onToggle={toggleUpcoming}
56+
emptyText="다가오는 일정이 없습니다"
57+
/>
58+
</Card>
59+
</div>
60+
);
61+
}

components/home/ButtonSection.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const ButtonSection = () => {
2+
return (
3+
<div className="flex w-full gap-6">
4+
<button
5+
type="button"
6+
className="bg-primary flex-1 rounded-xl py-4 text-center text-base font-semibold text-white shadow-sm transition hover:bg-violet-500 active:bg-violet-700 disabled:cursor-not-allowed disabled:opacity-60"
7+
>
8+
새 TIL 작성
9+
</button>
10+
11+
<button
12+
type="button"
13+
className="flex-1 rounded-xl border border-slate-300 bg-none py-4 text-center text-base font-semibold text-slate-700 transition hover:bg-slate-200 active:bg-slate-300 disabled:cursor-not-allowed disabled:opacity-60"
14+
>
15+
계획 추가하기
16+
</button>
17+
</div>
18+
);
19+
};
20+
21+
export default ButtonSection;

components/home/Card.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ReactNode } from 'react';
2+
3+
// 흰 바탕의 카드
4+
5+
interface CardProps {
6+
children: ReactNode; // 카드 내부 내용들
7+
className?: string; // 추가 스타일링
8+
title?: string; // 좌측 상단 제목이 있는 경우
9+
}
10+
11+
const Card = ({ children, className = '', title }: CardProps) => {
12+
return (
13+
<div
14+
className={`border-border bg-surface rounded-[10px] p-6 shadow-sm ${className}`}
15+
>
16+
{title && (
17+
<h2 className="mb-4 text-lg font-bold text-gray-900">{title}</h2>
18+
)}
19+
{children}
20+
</div>
21+
);
22+
};
23+
24+
export default Card;

components/home/CheckItem.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use client';
2+
3+
import { Check } from 'lucide-react';
4+
5+
type CheckItemProps = {
6+
checked: boolean;
7+
text: string;
8+
onToggle: () => void;
9+
};
10+
11+
export function CheckItem({ checked, text, onToggle }: CheckItemProps) {
12+
return (
13+
<button
14+
type="button"
15+
onClick={onToggle}
16+
className={[
17+
'flex w-full items-center gap-2.5 rounded-2xl border px-4 py-2 text-left transition',
18+
'bg-gray-50 hover:bg-gray-100',
19+
'border-gray-300',
20+
].join(' ')}
21+
>
22+
<div
23+
className={[
24+
'flex h-6 w-6 items-center justify-center rounded-full border-2 transition',
25+
checked ? 'border-green-500' : 'border-gray-400',
26+
].join(' ')}
27+
>
28+
<Check
29+
className={[
30+
'transition-all',
31+
checked
32+
? 'scale-100 text-green-500 opacity-100'
33+
: 'scale-50 opacity-0',
34+
].join(' ')}
35+
size={16}
36+
strokeWidth={4}
37+
/>
38+
</div>
39+
<span
40+
className={[
41+
'text-sm',
42+
checked ? 'text-gray-400 line-through' : 'text-gray-800',
43+
].join(' ')}
44+
>
45+
{text}
46+
</span>
47+
</button>
48+
);
49+
}

components/home/CheckList.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use client';
2+
3+
import { CheckItem } from './CheckItem';
4+
5+
export type ChecklistItem = {
6+
id: string;
7+
text: string;
8+
isChecked: boolean;
9+
};
10+
11+
interface CheckListProps {
12+
items: ChecklistItem[];
13+
onToggle: (id: string) => void;
14+
emptyText?: string;
15+
}
16+
17+
export default function CheckList({
18+
items,
19+
onToggle,
20+
emptyText = '아직 항목이 없습니다',
21+
}: CheckListProps) {
22+
if (items.length === 0) {
23+
return <p className="text-sm text-gray-400">{emptyText}</p>;
24+
}
25+
26+
return (
27+
<div className="space-y-2.5">
28+
{items.map((item) => (
29+
<CheckItem
30+
key={item.id}
31+
checked={item.isChecked}
32+
text={item.text}
33+
onToggle={() => onToggle(item.id)}
34+
/>
35+
))}
36+
</div>
37+
);
38+
}

components/home/GraphSection.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Card from './Card';
2+
3+
interface GraphSectionProps {
4+
className?: string;
5+
}
6+
7+
const GraphSection = ({ className }: GraphSectionProps) => {
8+
return (
9+
<div className={className}>
10+
<Card title="학습 기록">잔디그래프 구현 예정</Card>
11+
</div>
12+
);
13+
};
14+
15+
export default GraphSection;

0 commit comments

Comments
 (0)