Skip to content

Commit c0c1882

Browse files
committed
update: Improved UI with Framer Motion
1 parent 6445415 commit c0c1882

24 files changed

+1328
-469
lines changed

app/blog/[slug]/page.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { notFound } from "next/navigation"
22
import { getPostBySlug, getAllPosts } from "@/utils/getPosts"
3-
import Link from "next/link"
43
import Footer from "@/components/Footer"
54
import { Metadata } from "next"
6-
import PostContent from "@/components/PostContent" // Import the new client-side component
5+
import PostContent from "@/components/PostContent"
76

87
interface Params {
98
params: {
@@ -36,18 +35,10 @@ export default async function Post({ params }: Params) {
3635
}
3736

3837
return (
39-
<div className="min-h-screen bg-white">
40-
<div className="container max-w-[900px] w-full mx-auto px-10 py-8">
41-
<Link href="/#blog" className="text-black hover:underline mb-8 inline-block">
42-
← Back to all posts
43-
</Link>
44-
45-
{/* Render markdown content on the client */}
46-
<PostContent post={post as any} />
47-
</div>
48-
38+
<>
39+
<PostContent post={post as any} />
4940
<Footer />
50-
</div>
41+
</>
5142
)
5243
}
5344

@@ -57,4 +48,4 @@ export async function generateStaticParams() {
5748
return posts.map((post) => ({
5849
slug: post.slug,
5950
}))
60-
}
51+
}

app/blog/page.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { getAllPosts } from "@/utils/getPosts"
22
import type { PostData } from "@/types"
3-
import BlogCard from "@/components/BlogCard"
3+
import BlogPageClient from '@/components/BlogPage';
44

55
export default async function BlogPage() {
6+
67
const posts: PostData[] = getAllPosts([
78
"title",
89
"date",
@@ -13,18 +14,6 @@ export default async function BlogPage() {
1314
])
1415

1516
return (
16-
<section className="py-20 bg-white">
17-
<div className="container mx-auto px-4">
18-
<h1 className="text-4xl md:text-5xl font-bold text-black mb-16">
19-
All Blog Posts
20-
</h1>
21-
22-
<div className="grid md:grid-cols-3 gap-10">
23-
{posts.map((post) => (
24-
<BlogCard key={post.slug} post={post} />
25-
))}
26-
</div>
27-
</div>
28-
</section>
17+
<BlogPageClient posts={posts} />
2918
)
3019
}

app/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Hero from "@/components/sections/Hero"
22
import ToolsSection from "@/components/sections/ExploreSection"
3-
import ServicesSection from "@/components/sections/ServicesSection"
43
import WhyAgileCoder from "@/components/sections/WhyAgileCoder"
54
import BlogPreview from "@/components/sections/BlogPreview"
65
import About from "@/components/sections/About"
@@ -15,7 +14,6 @@ export default function Home() {
1514
<main className="min-h-screen">
1615
<Hero />
1716
<ToolsSection />
18-
<ServicesSection />
1917
<WhyAgileCoder />
2018
<BlogPreview posts={posts} />
2119
<About />

components/BlogCard.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

components/BlogPage.tsx

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
"use client"
2+
3+
import { PostData } from '@/types';
4+
import { motion } from 'framer-motion';
5+
import { Search, Calendar, TrendingUp, Clock } from 'lucide-react';
6+
import { useState, useMemo } from 'react';
7+
8+
interface BlogPageClientProps {
9+
posts: PostData[];
10+
}
11+
12+
export default async function BlogPageClient({ posts }: BlogPageClientProps) {
13+
const [searchQuery, setSearchQuery] = useState('');
14+
const [sortBy, setSortBy] = useState<'latest' | 'popular'>('latest');
15+
16+
const filteredPosts = useMemo(() => {
17+
let filtered = posts.filter(post =>
18+
post.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
19+
post.excerpt?.toLowerCase().includes(searchQuery.toLowerCase())
20+
);
21+
22+
if (sortBy === 'latest') {
23+
filtered.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
24+
}
25+
26+
return filtered;
27+
}, [searchQuery, sortBy]);
28+
29+
const containerVariants = {
30+
hidden: { opacity: 0 },
31+
visible: {
32+
opacity: 1,
33+
transition: {
34+
staggerChildren: 0.1
35+
}
36+
}
37+
} as const;
38+
39+
const cardVariants = {
40+
hidden: { opacity: 0, y: 30 },
41+
visible: {
42+
opacity: 1,
43+
y: 0
44+
}
45+
} as const;
46+
47+
return (
48+
<section className="py-20 bg-gray-50 min-h-screen">
49+
<div className="container mx-auto px-4">
50+
{/* Header Section */}
51+
<motion.div
52+
initial={{ opacity: 0, y: 20 }}
53+
animate={{ opacity: 1, y: 0 }}
54+
transition={{ duration: 0.6 }}
55+
className="mb-12"
56+
>
57+
<h1 className="text-4xl md:text-6xl font-bold text-black mb-4">
58+
All Blog Posts
59+
</h1>
60+
<p className="text-xl text-gray-600 max-w-2xl">
61+
Insights, tutorials, and best practices from our development journey
62+
</p>
63+
</motion.div>
64+
65+
{/* Search and Filter Bar */}
66+
<motion.div
67+
initial={{ opacity: 0, y: 20 }}
68+
animate={{ opacity: 1, y: 0 }}
69+
transition={{ duration: 0.6, delay: 0.2 }}
70+
className="mb-12 bg-white rounded-2xl shadow-sm p-6"
71+
>
72+
<div className="flex flex-col md:flex-row gap-4 items-center justify-between">
73+
{/* Search Input */}
74+
<div className="relative flex-1 w-full md:max-w-md">
75+
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
76+
<input
77+
type="text"
78+
placeholder="Search articles..."
79+
value={searchQuery}
80+
onChange={(e) => setSearchQuery(e.target.value)}
81+
className="w-full pl-12 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-black focus:border-black outline-none transition"
82+
/>
83+
</div>
84+
85+
{/* Sort Buttons */}
86+
<div className="flex gap-2">
87+
<button
88+
onClick={() => setSortBy('latest')}
89+
className={`flex items-center gap-2 px-4 py-3 rounded-xl font-medium transition ${sortBy === 'latest'
90+
? 'bg-black text-white'
91+
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
92+
}`}
93+
>
94+
<Clock className="h-4 w-4" />
95+
Latest
96+
</button>
97+
<button
98+
onClick={() => setSortBy('popular')}
99+
className={`flex items-center gap-2 px-4 py-3 rounded-xl font-medium transition ${sortBy === 'popular'
100+
? 'bg-black text-white'
101+
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
102+
}`}
103+
>
104+
<TrendingUp className="h-4 w-4" />
105+
Popular
106+
</button>
107+
</div>
108+
</div>
109+
110+
{/* Results Count */}
111+
<motion.div
112+
initial={{ opacity: 0 }}
113+
animate={{ opacity: 1 }}
114+
transition={{ delay: 0.3 }}
115+
className="mt-4 text-sm text-gray-600"
116+
>
117+
{filteredPosts.length} {filteredPosts.length === 1 ? 'article' : 'articles'} found
118+
</motion.div>
119+
</motion.div>
120+
121+
{/* Blog Grid */}
122+
{filteredPosts.length > 0 ? (
123+
<motion.div
124+
variants={containerVariants}
125+
initial="hidden"
126+
animate="visible"
127+
className="grid md:grid-cols-2 lg:grid-cols-3 gap-8"
128+
>
129+
{filteredPosts.map((post, index) => (
130+
<motion.article
131+
key={post.slug}
132+
variants={cardVariants}
133+
transition={{ duration: 0.5 }}
134+
whileHover={{ y: -8 }}
135+
className="group bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-xl transition-all duration-300"
136+
>
137+
<a href={`/blog/${post.slug}`} className="block">
138+
{/* Image */}
139+
<div className="relative h-48 overflow-hidden bg-gray-200">
140+
<motion.img
141+
whileHover={{ scale: 1.05 }}
142+
transition={{ duration: 0.4 }}
143+
src={post.coverImage}
144+
alt={post.title}
145+
className="w-full h-full object-cover"
146+
/>
147+
<div className="absolute inset-0 bg-black opacity-0 group-hover:opacity-10 transition-opacity duration-300" />
148+
</div>
149+
150+
{/* Content */}
151+
<div className="p-6">
152+
{/* Meta */}
153+
<div className="flex items-center gap-4 text-sm text-gray-500 mb-3">
154+
<span className="flex items-center gap-1">
155+
<Calendar className="h-4 w-4" />
156+
{new Date(post.date).toLocaleDateString("en-US", {
157+
month: "short",
158+
day: "numeric",
159+
year: "numeric"
160+
})}
161+
</span>
162+
</div>
163+
164+
{/* Title */}
165+
<h3 className="text-xl font-semibold mb-3 group-hover:text-black transition-colors line-clamp-2">
166+
{post.title}
167+
</h3>
168+
169+
{/* Excerpt */}
170+
<p className="text-gray-600 mb-4 line-clamp-3">
171+
{post.excerpt}
172+
</p>
173+
174+
{/* Read More */}
175+
<div className="flex items-center text-black font-medium">
176+
Read more
177+
<motion.span
178+
className="ml-1"
179+
animate={{ x: [0, 4, 0] }}
180+
transition={{ duration: 1.5, repeat: Infinity }}
181+
>
182+
183+
</motion.span>
184+
</div>
185+
</div>
186+
</a>
187+
</motion.article>
188+
))}
189+
</motion.div>
190+
) : (
191+
<motion.div
192+
initial={{ opacity: 0 }}
193+
animate={{ opacity: 1 }}
194+
className="text-center py-20"
195+
>
196+
<Search className="h-16 w-16 text-gray-400 mx-auto mb-4" />
197+
<h3 className="text-2xl font-semibold text-gray-700 mb-2">No articles found</h3>
198+
<p className="text-gray-500">Try adjusting your search query</p>
199+
</motion.div>
200+
)}
201+
</div>
202+
</section>
203+
)
204+
}

0 commit comments

Comments
 (0)