Skip to content

Commit 3dc18f3

Browse files
committed
feat: add blog pages with slug-based routing
- Add blog index page and individual post page with [slug] routing - Add BlogIndex, BlogPost, and BlogSidebar components - Add blog data module with sample posts
1 parent 8128c7d commit 3dc18f3

6 files changed

Lines changed: 904 additions & 0 deletions

File tree

src/app/blog/[slug]/page.tsx

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
'use client'
2+
3+
import Image from 'next/image'
4+
import Link from 'next/link'
5+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6+
import {
7+
faCalendar,
8+
faComments,
9+
faEnvelope,
10+
faFileLines,
11+
faHome,
12+
faReply,
13+
faSearchPlus,
14+
faUser,
15+
} from '@fortawesome/free-solid-svg-icons'
16+
import { faFacebook, faGooglePlus, faTwitter } from '@fortawesome/free-brands-svg-icons'
17+
import { Breadcrumbs, ActionButton, Separator } from '@/components/breadcrumbs'
18+
import { BlogSidebar } from '@/components/blog/blog-sidebar'
19+
20+
export default function BlogPostPage() {
21+
return (
22+
<div className="text-[#323232] bg-white pb-16">
23+
<Breadcrumbs
24+
items={[
25+
{ label: 'Home', href: '/', icon: <FontAwesomeIcon icon={faHome} className="text-[11px]" /> },
26+
{ label: 'Blog' },
27+
]}
28+
/>
29+
30+
<section className="mx-auto max-w-7xl px-6 pt-10 lg:px-8">
31+
<div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_320px]">
32+
<main className="space-y-8">
33+
<article className="border border-[#ececec] rounded bg-white shadow-[0_8px_24px_rgba(0,0,0,0.04)] p-6 lg:p-8">
34+
<h1 className="text-[34px] font-normal leading-[1.15] text-[#2a2a2a]">Blog Post</h1>
35+
<div className="mt-4 flex flex-wrap items-center gap-x-5 gap-y-3 text-[13px] text-[#777]">
36+
<span className="inline-flex items-center gap-2">
37+
<FontAwesomeIcon icon={faCalendar} />
38+
<span>12 May 2015</span>
39+
</span>
40+
<span className="inline-flex items-center gap-2">
41+
<FontAwesomeIcon icon={faUser} />
42+
<span>by John Doe</span>
43+
</span>
44+
<span className="inline-flex items-center gap-2">
45+
<FontAwesomeIcon icon={faComments} />
46+
<span>22 comments</span>
47+
</span>
48+
</div>
49+
50+
<div className="mt-6 space-y-4">
51+
{['/template/images/blog-1.jpg', '/template/images/blog-3.jpg', '/template/images/blog-4.jpg'].map(
52+
(src) => (
53+
<div key={src} className="group relative overflow-hidden rounded-[3px]">
54+
<Image src={src} alt="" width={1200} height={720} className="h-auto w-full" />
55+
<div className="absolute inset-0 flex items-center justify-center bg-black/45 opacity-0 transition-opacity duration-[160ms] ease-out group-hover:opacity-100">
56+
<span className="rounded-full bg-white/90 p-3 text-[#444]">
57+
<FontAwesomeIcon icon={faSearchPlus} />
58+
</span>
59+
</div>
60+
</div>
61+
)
62+
)}
63+
</div>
64+
65+
<div className="prose prose-neutral mt-8 max-w-none text-[15px] leading-8 text-[#666]">
66+
<p>
67+
Mauris dolor sapien, <Link href="#">malesuada at interdum ut</Link>, hendrerit eget lorem. Nunc
68+
interdum mi neque, et sollicitudin purus fermentum ut. Suspendisse faucibus nibh odio, a vehicula eros
69+
pharetra in. Maecenas ullamcorper commodo rutrum. In iaculis lectus vel augue eleifend dignissim.
70+
Aenean viverra semper sollicitudin.
71+
</p>
72+
<p>
73+
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Corporis ullam nemo itaque excepturi
74+
suscipit unde repudiandae nesciunt ad voluptates minima recusandae illum exercitationem, neque, ut
75+
totam ratione.
76+
</p>
77+
<ol>
78+
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi, laboriosam tempore.</li>
79+
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Corporis odit est quae amet iure.</li>
80+
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quam aperiam vel cum quisquam enim.</li>
81+
<li>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Illo, eaque.</li>
82+
</ol>
83+
<blockquote>
84+
<p>
85+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore
86+
et dolore magna aliqua.
87+
</p>
88+
</blockquote>
89+
<p>
90+
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ducimus incidunt, beatae rem omnis
91+
distinctio, dolor, praesentium impedit quisquam nobis pariatur nulla expedita aliquid repellendus
92+
laudantium.
93+
</p>
94+
</div>
95+
96+
<footer className="mt-8 flex flex-col gap-4 border-t border-[#efefef] pt-4 text-[13px] text-[#777] sm:flex-row sm:items-center sm:justify-between">
97+
<div>
98+
{['tag 1', 'tag 2', 'long tag 3'].map((tag, index, array) => (
99+
<span key={tag}>
100+
<Link href="#" className="text-[#09afdf] hover:underline">
101+
{tag}
102+
</Link>
103+
{index < array.length - 1 ? ', ' : ''}
104+
</span>
105+
))}
106+
</div>
107+
<ul className="flex gap-3">
108+
{[faTwitter, faGooglePlus, faFacebook].map((icon, index) => (
109+
<li key={index}>
110+
<Link
111+
href="#"
112+
className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-[#e3e3e3] text-[#666]"
113+
>
114+
<FontAwesomeIcon icon={icon} />
115+
</Link>
116+
</li>
117+
))}
118+
</ul>
119+
</footer>
120+
</article>
121+
122+
<section className="border border-[#ececec] rounded bg-white shadow-[0_8px_24px_rgba(0,0,0,0.04)] p-6 lg:p-8">
123+
<h2 className="text-[30px] font-normal text-[#2f2f2f]">There are 3 comments</h2>
124+
<div className="mt-6 space-y-6">
125+
{[1, 2, 3].map((item) => (
126+
<div key={item} className="flex gap-4 border-b border-[#f1f1f1] pb-6">
127+
<Image
128+
src="/template/images/avatar.jpg"
129+
alt="Avatar"
130+
width={60}
131+
height={60}
132+
className="h-[60px] w-[60px] rounded-full object-cover"
133+
/>
134+
<div className="min-w-0 flex-1">
135+
<header>
136+
<h3 className="text-[20px] font-normal text-[#2f2f2f]">Comment title</h3>
137+
<div className="text-[13px] text-[#777]">By admin | Today, 12:31</div>
138+
</header>
139+
<p className="mt-3 text-[15px] leading-8 text-[#666]">
140+
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut
141+
labore et dolore magna aliqua.
142+
</p>
143+
<Link
144+
href="#"
145+
className="mt-3 inline-flex items-center gap-2 text-[13px] text-[#444] hover:text-[#09afdf]"
146+
>
147+
<FontAwesomeIcon icon={faReply} />
148+
<span>Reply</span>
149+
</Link>
150+
</div>
151+
</div>
152+
))}
153+
</div>
154+
</section>
155+
156+
<section className="border border-[#ececec] rounded bg-white shadow-[0_8px_24px_rgba(0,0,0,0.04)] p-6 lg:p-8">
157+
<h2 className="text-[30px] font-normal text-[#2f2f2f]">Add your comment</h2>
158+
<form className="mt-6 space-y-5">
159+
{[
160+
{ label: 'Name', type: 'text', icon: faUser },
161+
{ label: 'Subject', type: 'text', icon: faFileLines },
162+
].map((field) => (
163+
<label key={field.label} className="block">
164+
<span className="mb-2 block text-[14px] text-[#555]">{field.label}</span>
165+
<div className="relative">
166+
<input
167+
className="w-full rounded-[3px] border border-[#ddd] px-4 py-3 pr-10 outline-none focus:border-[#09afdf]"
168+
type={field.type}
169+
/>
170+
<FontAwesomeIcon
171+
icon={field.icon}
172+
className="absolute right-4 top-1/2 -translate-y-1/2 text-[#aaa]"
173+
/>
174+
</div>
175+
</label>
176+
))}
177+
<label className="block">
178+
<span className="mb-2 block text-[14px] text-[#555]">Message</span>
179+
<div className="relative">
180+
<textarea
181+
rows={8}
182+
className="w-full rounded-[3px] border border-[#ddd] px-4 py-3 pr-10 outline-none focus:border-[#09afdf]"
183+
/>
184+
<FontAwesomeIcon icon={faEnvelope} className="absolute right-4 top-4 text-[#aaa]" />
185+
</div>
186+
</label>
187+
<ActionButton variant="primary">Submit</ActionButton>
188+
</form>
189+
</section>
190+
</main>
191+
192+
<BlogSidebar />
193+
</div>
194+
</section>
195+
</div>
196+
)
197+
}

src/app/blog/page.tsx

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
'use client'
2+
3+
import Image from 'next/image'
4+
import Link from 'next/link'
5+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
6+
import { faArrowUpRightFromSquare, faCalendar, faComments, faHome, faUser } from '@fortawesome/free-solid-svg-icons'
7+
import { blogPosts } from '@/components/blog/data'
8+
import { cn } from '@/lib/cn'
9+
import { Breadcrumbs, Separator } from '@/components/breadcrumbs'
10+
import { BlogSidebar } from '@/components/blog/blog-sidebar'
11+
12+
function BlogPostCard({ post }: { post: (typeof blogPosts)[number] }) {
13+
return (
14+
<article className="border border-[#ececec] rounded bg-white shadow-[0_8px_24px_rgba(0,0,0,0.04)] p-5 lg:p-6">
15+
<div className="grid gap-6 md:grid-cols-2">
16+
<div>
17+
{post.type === 'slider' ? (
18+
<div className="space-y-3">
19+
{post.images?.map((image) => (
20+
<div key={image} className="group relative overflow-hidden rounded-[3px]">
21+
<Image src={image} alt="" width={900} height={560} className="h-auto w-full" />
22+
<div className="absolute inset-0 flex items-center justify-center bg-black/45 opacity-0 transition-opacity duration-[160ms] ease-out group-hover:opacity-100">
23+
<Link href={`/blog/${post.slug}`} className="rounded-full bg-white/90 p-3 text-[#444]">
24+
<FontAwesomeIcon icon={faArrowUpRightFromSquare} />
25+
</Link>
26+
</div>
27+
</div>
28+
))}
29+
</div>
30+
) : null}
31+
32+
{post.type === 'image' ? (
33+
<div className="group relative overflow-hidden rounded-[3px]">
34+
<Image
35+
src={post.image ?? '/template/images/blog-2.jpg'}
36+
alt=""
37+
width={900}
38+
height={560}
39+
className="h-auto w-full"
40+
/>
41+
<div className="absolute inset-0 flex items-center justify-center bg-black/45 opacity-0 transition-opacity duration-[160ms] ease-out group-hover:opacity-100">
42+
<Link href={`/blog/${post.slug}`} className="rounded-full bg-white/90 p-3 text-[#444]">
43+
<FontAwesomeIcon icon={faArrowUpRightFromSquare} />
44+
</Link>
45+
</div>
46+
</div>
47+
) : null}
48+
49+
{post.type === 'audio' ? (
50+
<iframe
51+
className="h-[166px] w-full rounded-[3px] border-0"
52+
src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/231321623&color=ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false"
53+
title="Audio post"
54+
/>
55+
) : null}
56+
57+
{post.type === 'video' ? (
58+
<div className="aspect-video overflow-hidden rounded-[3px] bg-black">
59+
<iframe
60+
className="h-full w-full"
61+
src="https://www.youtube.com/embed/91J8pLHdDB0"
62+
title="Blog video"
63+
allowFullScreen
64+
/>
65+
</div>
66+
) : null}
67+
</div>
68+
69+
<div>
70+
<header>
71+
<h2 className="text-[30px] font-normal leading-tight text-[#2f2f2f]">
72+
<Link href={`/blog/${post.slug}`} className="hover:text-[#09afdf]">
73+
{post.title}
74+
</Link>
75+
</h2>
76+
<div className="mt-4 flex flex-wrap items-center gap-x-5 gap-y-3 text-[13px] text-[#777]">
77+
<span className="inline-flex items-center gap-2">
78+
<FontAwesomeIcon icon={faCalendar} />
79+
<span>
80+
{post.dateDay} {post.dateMonth}
81+
</span>
82+
</span>
83+
<span className="inline-flex items-center gap-2">
84+
<FontAwesomeIcon icon={faUser} />
85+
<span>by {post.author}</span>
86+
</span>
87+
<span className="inline-flex items-center gap-2">
88+
<FontAwesomeIcon icon={faComments} />
89+
<span>{post.comments}</span>
90+
</span>
91+
</div>
92+
</header>
93+
<div className="mt-5 text-[15px] leading-8 text-[#666]">{post.excerpt}</div>
94+
</div>
95+
</div>
96+
<footer className="mt-6 flex flex-col gap-4 border-t border-[#efefef] pt-4 text-[13px] text-[#777] sm:flex-row sm:items-center sm:justify-between">
97+
<div>
98+
{post.tags.map((tag, index) => (
99+
<span key={tag}>
100+
<Link href="#" className="text-[#09afdf] hover:underline">
101+
{tag}
102+
</Link>
103+
{index < post.tags.length - 1 ? ', ' : ''}
104+
</span>
105+
))}
106+
</div>
107+
<Link href={`/blog/${post.slug}`} className="text-[#09afdf] hover:underline">
108+
Read More
109+
</Link>
110+
</footer>
111+
</article>
112+
)
113+
}
114+
115+
export default function BlogPage() {
116+
return (
117+
<div className="text-[#323232] bg-white pb-16">
118+
<Breadcrumbs
119+
items={[
120+
{ label: 'Home', href: '/', icon: <FontAwesomeIcon icon={faHome} className="text-[11px]" /> },
121+
{ label: 'Blog' },
122+
]}
123+
/>
124+
125+
<section className="mx-auto max-w-7xl px-6 pt-6 lg:px-8">
126+
<div className="grid gap-8 lg:grid-cols-[minmax(0,1fr)_320px]">
127+
<main className="space-y-6">
128+
<div>
129+
<h1 className="text-[34px] font-normal leading-[1.15] text-[#2a2a2a] flex items-center gap-3">
130+
<FontAwesomeIcon icon={faComments} />
131+
<span>CodeBuilder Inc. Blog</span>
132+
</h1>
133+
<Separator />
134+
</div>
135+
{blogPosts.map((post) => (
136+
<BlogPostCard key={post.slug} post={post} />
137+
))}
138+
139+
<nav>
140+
<ul className="flex flex-wrap gap-2 text-[14px]">
141+
{['1', '2', '3', '4', '5'].map((page, index) => (
142+
<li key={page}>
143+
<Link
144+
href="#"
145+
className={cn(
146+
'inline-flex h-10 min-w-10 items-center justify-center rounded-[3px] border px-3',
147+
index === 0 ? 'border-[#09afdf] bg-[#09afdf] text-white' : 'border-[#e0e0e0] text-[#666]'
148+
)}
149+
>
150+
{page}
151+
</Link>
152+
</li>
153+
))}
154+
</ul>
155+
</nav>
156+
</main>
157+
158+
<BlogSidebar />
159+
</div>
160+
</section>
161+
</div>
162+
)
163+
}

0 commit comments

Comments
 (0)