Skip to content

Commit 28d2ee9

Browse files
committed
Merge branch 'dev' into blog
2 parents 4e92a97 + 5fd1830 commit 28d2ee9

25 files changed

+1789
-220
lines changed

app/(blog)/page.tsx

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
import PostCard from "@/components/post-card";
22
import { getPosts } from "@/lib/posts";
3+
import { HeaderSection, PostGrid, PostItem } from "@/components/home-animation";
4+
// Import global CSS classes for typography
35

46
export default async function Home() {
57
const posts = await getPosts();
68

79
return (
810
<div className="space-y-6 sm:space-y-8 2xl:space-y-10 h-full rounded-lg p-1 sm:p-7">
911
{/* 헤더 섹션 */}
10-
<section className="space-y-2 sm:space-y-3 md:space-y-4 ">
11-
<h1 className="text-lg sm:text-xl lg:text-2xl font-bold">
12-
{`Lazydino's DevLog`}
13-
</h1>
14-
<p className="text-sm sm:text-base text-muted-foreground">
15-
내가 한걸 티내기 위해 만든 블로그
16-
</p>
17-
</section>
12+
<HeaderSection
13+
title="Lazydino's DevLog"
14+
description="내가 한걸 티내기 위해 만든 블로그"
15+
/>
1816

1917
{/* 포스트 그리드 */}
20-
<section>
21-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
22-
{posts.map((post) => (
18+
<PostGrid>
19+
{posts.map((post, index) => (
20+
<PostItem key={post.urlPath} index={index}>
2321
<PostCard
24-
key={post.urlPath}
2522
urlPath={post.urlPath}
2623
title={post.title}
2724
summary={post.summary}
@@ -31,9 +28,9 @@ export default async function Home() {
3128
tags={post.tags}
3229
createdAt={post.createdAt}
3330
/>
34-
))}
35-
</div>
36-
</section>
31+
</PostItem>
32+
))}
33+
</PostGrid>
3734
</div>
3835
);
39-
}
36+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"use client";
2+
3+
import React from "react";
4+
import MarkdownRenderer from "@/components/markdown-renderer";
5+
import { BackToHomeButton } from "@/components/post-animation";
6+
import ScrollToTop from "@/components/scroll-to-top";
7+
import { Post } from "@/lib/posts";
8+
import Link from "next/link";
9+
import { motion } from "framer-motion";
10+
import { ArrowLeft, ArrowRight } from "lucide-react";
11+
12+
interface PostContentProps {
13+
content: string;
14+
publishPath: string;
15+
published: string;
16+
modified: string;
17+
tags: string[];
18+
prevPost: Post | null;
19+
nextPost: Post | null;
20+
}
21+
22+
export default function PostContent({
23+
content,
24+
publishPath,
25+
published,
26+
modified,
27+
tags,
28+
prevPost,
29+
nextPost,
30+
}: PostContentProps) {
31+
return (
32+
<>
33+
<div className="flex flex-col lg:flex-row gap-6">
34+
{/* 콘텐츠 부분 */}
35+
<div className="lg:w-[calc(100%)]">
36+
<div className="min-h-[250px] sm:min-h-[300px]">
37+
<MarkdownRenderer
38+
content={content}
39+
publish={publishPath}
40+
published={published}
41+
modified={modified}
42+
tags={tags}
43+
/>
44+
</div>
45+
46+
{/* 이전글/다음글 네비게이션 */}
47+
<nav className="mt-8 sm:mt-10 pt-4 sm:pt-6 border-t">
48+
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
49+
{prevPost ? (
50+
<motion.div
51+
className="w-full sm:w-auto"
52+
whileHover={{ x: -3 }}
53+
transition={{ type: "spring", stiffness: 400, damping: 15 }}
54+
>
55+
<Link
56+
href={`/posts/${prevPost.urlPath}`}
57+
className="flex items-center text-muted-foreground hover:text-primary transition-colors"
58+
>
59+
<ArrowLeft className="mr-2 h-4 w-4" />
60+
<div>
61+
<div className="text-xs mb-1">이전 글</div>
62+
<div className="text-sm font-medium truncate max-w-[250px]">{prevPost.title}</div>
63+
</div>
64+
</Link>
65+
</motion.div>
66+
) : (
67+
<div className="w-full sm:w-auto opacity-50">
68+
<div className="flex items-center text-muted-foreground">
69+
<ArrowLeft className="mr-2 h-4 w-4" />
70+
<div>
71+
<div className="text-xs mb-1">이전 글</div>
72+
<div className="text-sm font-medium">없음</div>
73+
</div>
74+
</div>
75+
</div>
76+
)}
77+
78+
{nextPost ? (
79+
<motion.div
80+
className="w-full sm:w-auto text-right"
81+
whileHover={{ x: 3 }}
82+
transition={{ type: "spring", stiffness: 400, damping: 15 }}
83+
>
84+
<Link
85+
href={`/posts/${nextPost.urlPath}`}
86+
className="flex items-center justify-end text-muted-foreground hover:text-primary transition-colors"
87+
>
88+
<div>
89+
<div className="text-xs mb-1">다음 글</div>
90+
<div className="text-sm font-medium truncate max-w-[250px]">{nextPost.title}</div>
91+
</div>
92+
<ArrowRight className="ml-2 h-4 w-4" />
93+
</Link>
94+
</motion.div>
95+
) : (
96+
<div className="w-full sm:w-auto text-right opacity-50">
97+
<div className="flex items-center justify-end text-muted-foreground">
98+
<div>
99+
<div className="text-xs mb-1">다음 글</div>
100+
<div className="text-sm font-medium">없음</div>
101+
</div>
102+
<ArrowRight className="ml-2 h-4 w-4" />
103+
</div>
104+
</div>
105+
)}
106+
</div>
107+
</nav>
108+
109+
<BackToHomeButton />
110+
</div>
111+
</div>
112+
<ScrollToTop />
113+
</>
114+
);
115+
}

app/projects/page.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import { Metadata } from "next";
2+
import ProjectsPage from "./projects-component";
3+
4+
export const metadata: Metadata = {
5+
title: "프로젝트 | lazydino",
6+
description: "개발자 lazydino의 프로젝트 포트폴리오",
7+
};
8+
19
export default function Projects() {
2-
return <div>Project</div>;
10+
// 서버 컴포넌트에서는 데이터를 가져오고, 클라이언트 컴포넌트에 전달
11+
return <ProjectsPage />;
312
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"use client";
2+
3+
import React from "react";
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogHeader,
8+
DialogTitle,
9+
DialogDescription,
10+
DialogClose
11+
} from "@/components/ui/dialog";
12+
import { Button } from "@/components/ui/button";
13+
import { Badge } from "@/components/ui/badge";
14+
import { ExternalLink, Github, X } from "lucide-react";
15+
import Image from "next/image";
16+
import Link from "next/link";
17+
import { Project } from "./types";
18+
19+
interface ProjectDetailDialogProps {
20+
project: Project | null;
21+
open: boolean;
22+
onOpenChange: (open: boolean) => void;
23+
}
24+
25+
export default function ProjectDetailDialog({
26+
project,
27+
open,
28+
onOpenChange,
29+
}: ProjectDetailDialogProps) {
30+
if (!project) return null;
31+
32+
return (
33+
<Dialog open={open} onOpenChange={onOpenChange}>
34+
<DialogContent className="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
35+
<DialogHeader>
36+
<div className="flex justify-between items-center">
37+
<DialogTitle className="text-2xl">{project.title}</DialogTitle>
38+
<DialogClose asChild>
39+
<Button variant="ghost" size="icon">
40+
<X className="h-4 w-4" />
41+
</Button>
42+
</DialogClose>
43+
</div>
44+
<DialogDescription>
45+
<div className="flex flex-wrap gap-1 mt-2">
46+
{project.tags.map((tag) => (
47+
<Badge key={tag} variant="outline" className="text-xs">
48+
{tag}
49+
</Badge>
50+
))}
51+
</div>
52+
</DialogDescription>
53+
</DialogHeader>
54+
55+
<div className="mt-4 overflow-hidden rounded-lg">
56+
<Image
57+
src={project.thumbnail || "/postImg/project/default-project.png"}
58+
alt={project.title}
59+
width={800}
60+
height={450}
61+
className="w-full object-cover"
62+
/>
63+
</div>
64+
65+
<div className="mt-6">
66+
<h3 className="text-lg font-medium mb-2">프로젝트 개요</h3>
67+
<p className="text-muted-foreground leading-relaxed">
68+
{project.description}
69+
</p>
70+
</div>
71+
72+
<div className="mt-6">
73+
<h3 className="text-lg font-medium mb-2">사용 기술</h3>
74+
<div className="flex flex-wrap gap-2">
75+
{project.technologies.map((tech) => (
76+
<Badge key={tech} variant="secondary" className="text-sm">
77+
{tech}
78+
</Badge>
79+
))}
80+
</div>
81+
</div>
82+
83+
<div className="mt-6">
84+
<h3 className="text-lg font-medium mb-2">주요 기능</h3>
85+
<ul className="list-disc pl-5 space-y-1 text-muted-foreground">
86+
<li>기능 1에 대한 설명이 들어갑니다.</li>
87+
<li>기능 2에 대한 설명이 들어갑니다.</li>
88+
<li>기능 3에 대한 설명이 들어갑니다.</li>
89+
</ul>
90+
</div>
91+
92+
<div className="mt-6">
93+
<h3 className="text-lg font-medium mb-2">배운 점</h3>
94+
<p className="text-muted-foreground leading-relaxed">
95+
이 프로젝트를 통해 배운 점이나 도전 과제, 해결 방법 등을 설명합니다.
96+
</p>
97+
</div>
98+
99+
<div className="mt-6 flex flex-wrap gap-3">
100+
{project.githubUrl && (
101+
<Link href={project.githubUrl} target="_blank" rel="noopener noreferrer">
102+
<Button variant="outline" size="sm">
103+
<Github className="mr-2 h-4 w-4" />
104+
GitHub 저장소
105+
</Button>
106+
</Link>
107+
)}
108+
{project.demoUrl && (
109+
<Link href={project.demoUrl} target="_blank" rel="noopener noreferrer">
110+
<Button variant="outline" size="sm">
111+
<ExternalLink className="mr-2 h-4 w-4" />
112+
라이브 데모
113+
</Button>
114+
</Link>
115+
)}
116+
</div>
117+
</DialogContent>
118+
</Dialog>
119+
);
120+
}

0 commit comments

Comments
 (0)