Skip to content

Commit 0be8cce

Browse files
Merge pull request #64 from DeveloperBlog-Devflow/dev
feat: v1.0.0 릴리즈
2 parents 6eb307c + 4414d0f commit 0be8cce

76 files changed

Lines changed: 10935 additions & 3182 deletions

Some content is hidden

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

animations/landingGSAP.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { gsap } from 'gsap';
2+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
3+
4+
let pluginsRegistered = false;
5+
6+
interface AnimateOptions {
7+
trigger: HTMLElement; // 스크롤 트리거 기준 요소
8+
targets: gsap.TweenTarget; // 애니메이션 대상
9+
stagger?: number; // 순차 재생 간격
10+
start?: string; // 스크롤 시작 지점
11+
yOffset?: number; // y축 이동 거리
12+
}
13+
14+
/* Fade Up 함수 */
15+
const animateFadeUp = ({
16+
trigger,
17+
targets,
18+
stagger = 0,
19+
start = 'top center-=50',
20+
yOffset = 24,
21+
}: AnimateOptions) => {
22+
if (!pluginsRegistered) {
23+
gsap.registerPlugin(ScrollTrigger);
24+
pluginsRegistered = true;
25+
}
26+
27+
const ctx = gsap.context(() => {
28+
gsap.fromTo(
29+
targets,
30+
{ opacity: 0, y: yOffset },
31+
{
32+
opacity: 1,
33+
y: 0,
34+
duration: 0.6,
35+
ease: 'power2.out',
36+
stagger: stagger,
37+
scrollTrigger: {
38+
trigger: trigger,
39+
start: start,
40+
toggleActions: 'play none none reverse',
41+
// markers: true,
42+
},
43+
}
44+
);
45+
});
46+
47+
return () => ctx.revert();
48+
};
49+
50+
/* --- 1. Hero Section --- */
51+
export type HeroSectionRefs = {
52+
titleEl: HTMLHeadingElement;
53+
subTextEl: HTMLDivElement;
54+
};
55+
56+
export const initHeroSectionAnimation = ({
57+
titleEl,
58+
subTextEl,
59+
}: HeroSectionRefs) => {
60+
return animateFadeUp({
61+
trigger: titleEl,
62+
targets: subTextEl,
63+
start: 'top center-=150',
64+
yOffset: 40,
65+
});
66+
};
67+
68+
/* --- 2. Feature List Section --- */
69+
export type FeatureListRefs = {
70+
titleEl: HTMLHeadingElement;
71+
subTextEl: HTMLDivElement;
72+
cardsContainerEl: HTMLDivElement;
73+
};
74+
75+
export const initFeatureListAnimation = ({
76+
titleEl,
77+
subTextEl,
78+
cardsContainerEl,
79+
}: FeatureListRefs) => {
80+
const cards = Array.from(cardsContainerEl.children);
81+
82+
return animateFadeUp({
83+
trigger: titleEl,
84+
targets: [subTextEl, ...cards],
85+
stagger: 0.12,
86+
});
87+
};
88+
89+
/* --- 3. Landing Preview Section --- */
90+
export type LandingPreviewRefs = {
91+
titleEl: HTMLHeadingElement;
92+
contentContainerEl: HTMLDivElement;
93+
};
94+
95+
export const initLandingPreviewAnimation = ({
96+
titleEl,
97+
contentContainerEl,
98+
}: LandingPreviewRefs) => {
99+
const items = contentContainerEl.querySelectorAll('.fc-item');
100+
101+
return animateFadeUp({
102+
trigger: titleEl,
103+
targets: items,
104+
stagger: 0.2,
105+
});
106+
};

app/(auth)/login/page.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use client';
2+
3+
import AuthCard from '@/components/auth/AuthCard';
4+
import FindPasswordModal from '@/components/auth/FindPasswordModal';
5+
import LoginForm from '@/components/auth/LoginForm';
6+
import { useState } from 'react';
7+
8+
const Page = () => {
9+
// 비밀번호 재설정 모달 state
10+
const [isModalOpen, setIsModalOpen] = useState(false);
11+
12+
// 모달 열기 & 닫기 메서드
13+
const handleOpenModal = () => {
14+
setIsModalOpen(true);
15+
};
16+
const handleCloseModal = () => {
17+
setIsModalOpen(false);
18+
};
19+
20+
return (
21+
<div className="bg-background">
22+
<AuthCard title="로그인" description="로그인해서 학습을 이어가세요">
23+
<LoginForm handleOpenModal={handleOpenModal} />
24+
</AuthCard>
25+
{isModalOpen && <FindPasswordModal onClose={handleCloseModal} />}
26+
</div>
27+
);
28+
};
29+
30+
export default Page;

app/(auth)/signup/page.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import AuthCard from '@/components/auth/AuthCard';
2+
import SignupForm from '@/components/auth/SignupForm/SignupForm';
3+
4+
const Page = () => {
5+
return (
6+
<div className="bg-background">
7+
<AuthCard title="회원가입" description="회원가입하여 계정을 생성하세요">
8+
<SignupForm />
9+
</AuthCard>
10+
</div>
11+
);
12+
};
13+
14+
export default Page;

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use client';
2+
3+
import BottomSection from '@/components/home/BottomSection';
4+
import GraphSection from '@/components/home/GraphSection';
5+
import HeaderSection from '@/components/home/HeaderSection';
6+
import ProfileSection from '@/components/home/ProfileSection';
7+
import ButtonSection from '@/components/home/ButtonSection';
8+
9+
import { useEffect, useState } from 'react';
10+
import { useAuthUser } from '@/hooks/useAuthUser';
11+
import { useTodayPlanItems } from '@/hooks/useTodayPlanItems';
12+
import { getProfile, Profile } from '@/services/home/profileService.service';
13+
14+
const Page = () => {
15+
// 현재 사용자 정보
16+
const { user: currentUser, authLoading } = useAuthUser();
17+
const [profile, setProfile] = useState<Profile | null>(null);
18+
const [error, setError] = useState<string | null>(null);
19+
20+
const {
21+
items,
22+
loading: planLoading,
23+
error: planError,
24+
toggle,
25+
progressText,
26+
total,
27+
} = useTodayPlanItems(currentUser?.uid);
28+
29+
// 사용자가 존재하면 데이터 불러옴
30+
useEffect(() => {
31+
if (!currentUser) return;
32+
(async () => {
33+
try {
34+
const userProfile = await getProfile(currentUser.uid);
35+
setProfile(userProfile);
36+
} catch (err) {
37+
console.error(err);
38+
setError('프로필 정보를 불러오는 데 실패하였습니다');
39+
}
40+
})();
41+
}, [currentUser]);
42+
43+
// 유저 정보 로딩 처리
44+
if (authLoading) {
45+
return (
46+
<div className="flex min-h-screen items-center justify-center">
47+
<p>로그인 확인 중...</p>
48+
</div>
49+
);
50+
}
51+
52+
// 유저 정보가 없을 시
53+
if (!currentUser) {
54+
return (
55+
<div className="flex min-h-screen items-center justify-center">
56+
<p>로그인이 필요합니다.</p>
57+
</div>
58+
);
59+
}
60+
61+
// 유저 정보가 있을 시
62+
return (
63+
<div className="bg-background flex min-h-screen flex-col gap-4 font-sans md:p-[137px]">
64+
{/* 1. Header */}
65+
<HeaderSection />
66+
67+
{/* 2-1. ProfileSection */}
68+
<ProfileSection
69+
className="grid grid-cols-1 gap-4 md:grid-cols-3"
70+
profile={profile}
71+
uid={currentUser.uid}
72+
progressText={progressText}
73+
/>
74+
75+
{/* 2-2. GraphSection */}
76+
<GraphSection uid={currentUser.uid}></GraphSection>
77+
78+
{/* 2-3. BottomSection */}
79+
<BottomSection
80+
uid={currentUser.uid}
81+
className="grid grid-cols-1 gap-4 md:grid-cols-2"
82+
items={items}
83+
loading={planLoading}
84+
error={planError}
85+
onToggle={toggle}
86+
todoTotal={total}
87+
/>
88+
89+
{/* 3. ButtonSection */}
90+
<ButtonSection />
91+
</div>
92+
);
93+
};
94+
95+
export default Page;
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: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use client';
2+
3+
import Sidebar from '@/components/common/Sidebar';
4+
import { useAuthGuard } from '@/hooks/useAuthGuard';
5+
6+
import '@uiw/react-md-editor/markdown-editor.css';
7+
import '@uiw/react-markdown-preview/markdown.css';
8+
9+
export default function SidebarLayout({
10+
children,
11+
}: {
12+
children: React.ReactNode;
13+
}) {
14+
const { loading, isAuthed } = useAuthGuard({ redirectTo: '/login' });
15+
16+
if (loading) return <div>로딩중...</div>;
17+
if (!isAuthed) {
18+
return null;
19+
}
20+
21+
return (
22+
<div className="flex">
23+
<Sidebar />
24+
<main className="bg-background flex-1 pl-48">{children}</main>
25+
</div>
26+
);
27+
}

0 commit comments

Comments
 (0)