Skip to content

Commit cfbd93e

Browse files
Merge pull request #3 from techgaun-np/feat/blog
blog section
2 parents 71f53f8 + cef16b3 commit cfbd93e

18 files changed

Lines changed: 752 additions & 3 deletions

File tree

app/(public)/blog/[id]/page.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use client";
2+
import { notFound } from "next/navigation";
3+
import React, { useEffect, useState } from "react";
4+
import { useParams } from "next/navigation";
5+
import BlogDeatails from "@/components/blog/details";
6+
import { blogData } from "@/static/blog";
7+
import { BlogItem } from "@/types/blog";
8+
9+
const page = () => {
10+
const params = useParams();
11+
const [isLoading, setIsLoading] = useState(true);
12+
const [post, setPost] = useState<BlogItem | null>(null);
13+
14+
useEffect(() => {
15+
if (params.id) {
16+
const foundPost = blogData.find((item) => item.id === params.id);
17+
setPost(foundPost || null);
18+
setIsLoading(false);
19+
}
20+
}, [params.id]);
21+
22+
if (isLoading) {
23+
return <div>Loading...</div>;
24+
}
25+
26+
if (!post) {
27+
notFound();
28+
}
29+
30+
return (
31+
<div>
32+
<BlogDeatails
33+
id={post.id}
34+
image={post.image}
35+
title={post.title}
36+
description={post.description}
37+
content={post.content}
38+
author={post.author}
39+
/>
40+
</div>
41+
);
42+
};
43+
44+
export default page;

app/(public)/blog/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import BlogContent from "@/components/blog";
2+
3+
const page = () => {
4+
return (
5+
<div>
6+
<BlogContent />
7+
</div>
8+
);
9+
};
10+
11+
export default page;

app/globals.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
--radius-md: calc(var(--radius) - 2px);
4444
--radius-lg: var(--radius);
4545
--radius-xl: calc(var(--radius) + 4px);
46+
--color-baby-aqua: var(--baby-aqua);
47+
--color-baby-aqua: var(--baby-aqua);
48+
--color-olive-drab: var(--olive-drab);
49+
--color-cloudCream: var(--cloudCream);
50+
--color-deepTeal: #005f66;
51+
--color-richBlack: #003033;
4652
}
4753

4854
:root {
@@ -80,6 +86,10 @@
8086
--sidebar-accent-foreground: oklch(0.205 0 0);
8187
--sidebar-border: oklch(0.922 0 0);
8288
--sidebar-ring: oklch(0.708 0 0);
89+
--baby-aqua: #e5fdff;
90+
--olive-drab: #454230;
91+
--cloudCream: #ebebe9;
92+
--deepTeal: #005f66;
8393
}
8494

8595
.dark {

app/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ export default function RootLayout({
3030
return (
3131
<html lang="en">
3232
<body
33-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
33+
className={`flex flex-col min-h-screen ${geistSans.variable} ${geistMono.variable} antialiased`}
3434
>
3535
<Navbar />
36-
{children}
36+
<main className="flex-1 w-full">{children}</main>
3737
<Footer />
3838
</body>
3939
</html>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
import React from "react";
3+
import HeroSectionCommon from "../common/HeroSectionCommon";
4+
5+
type Props = {
6+
title: string;
7+
};
8+
9+
const BlogSectionHero = ({ title }: Props) => {
10+
return (
11+
<div>
12+
<HeroSectionCommon
13+
imageSrc="/about/about-hero.jpg"
14+
title={title}
15+
description="Playing with heart, building a legacy."
16+
className="h-[100px] md:!h-[400px]"
17+
/>
18+
</div>
19+
);
20+
};
21+
22+
export default BlogSectionHero;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"use client";
2+
import React from "react";
3+
import { BlogContent } from "@/types/blog";
4+
import { blogData } from "@/static/blog";
5+
import Image from "next/image";
6+
import PopularCard from "./PopularCard";
7+
import { ArrowRight } from "lucide-react";
8+
import Link from "next/link";
9+
10+
type Props = {
11+
content: BlogContent;
12+
};
13+
14+
const DetailContent = ({ content }: Props) => {
15+
const popularList = blogData.filter((item) => item.isPopular);
16+
17+
return (
18+
<div className="flex flex-col md:grid md:grid-cols-8 gap-4 px-4 py-4">
19+
{/* left part - contents */}
20+
<div className="md:col-span-5 flex flex-col gap-4 py-4">
21+
<p>{content.description}</p>
22+
23+
{content.keyPerformance && (
24+
<ul className="list-disc pl-5">
25+
{content.keyPerformance.map((item, idx) => (
26+
<li key={idx}>{item}</li>
27+
))}
28+
</ul>
29+
)}
30+
31+
{content.images && content.images.length > 0 && (
32+
<div className="grid grid-cols-2 gap-2">
33+
{content.images.map((img, idx) => (
34+
<div key={idx} className="w-full h-42 relative">
35+
<Image
36+
src={img}
37+
alt={`content-img-${idx}`}
38+
className="object-cover w-full h-full cursor-pointer"
39+
/>
40+
</div>
41+
))}
42+
</div>
43+
)}
44+
45+
{content.matchHighlight && content.matchHighlight.length > 0 && (
46+
<div className="">
47+
<h2 className="text-lg font-medium mb-2">
48+
<span>📊</span>
49+
Match Highlights
50+
</h2>
51+
<ul className="list-disc pl-5">
52+
{content.matchHighlight.map((highlight, idx) => (
53+
<li key={idx}>{highlight}</li>
54+
))}
55+
</ul>
56+
</div>
57+
)}
58+
</div>
59+
60+
{/* right part - popular card */}
61+
<div className="md:col-span-3">
62+
<div className="flex flex-col gap-2">
63+
<div className="flex w-full justify-between px-2 items-center text-richBlack">
64+
<p className="text-richBlack text-[1rem] font-bold">Most Viewed</p>
65+
<Link
66+
href={`/blog?filter=popular`}
67+
className="flex items-center gap-1"
68+
>
69+
<p className="text-richBlack text-[0.75rem]">View all</p>
70+
<ArrowRight className="text-richBlack w-4 h-4" />
71+
</Link>
72+
</div>
73+
<div className="flex flex-col gap-2 px-2 py-4">
74+
{popularList.slice(0, 3).map((list, idx) => {
75+
return (
76+
<PopularCard
77+
key={idx}
78+
title={list.title}
79+
description={list.description}
80+
imageSrc={list.image}
81+
views={2456}
82+
/>
83+
);
84+
})}
85+
</div>
86+
</div>
87+
</div>
88+
</div>
89+
);
90+
};
91+
92+
export default DetailContent;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { cn } from "@/lib/utils";
5+
import Image from "next/image";
6+
import userImg from "@/public/common/user.png";
7+
import { CalendarRange, Clock3 } from "lucide-react";
8+
9+
interface HeroSectionProps {
10+
imageSrc: string;
11+
title: string;
12+
authorName: string;
13+
authorAvatar: string;
14+
date: string;
15+
readTime: string;
16+
className?: string;
17+
}
18+
19+
const DetailHero: React.FC<HeroSectionProps> = ({
20+
imageSrc,
21+
title,
22+
authorName,
23+
authorAvatar,
24+
date,
25+
readTime,
26+
className,
27+
}) => {
28+
return (
29+
<div className={cn("relative w-full h-[470px] md:h-[570px]", className)}>
30+
{/* Background Image */}
31+
<Image
32+
src={imageSrc}
33+
alt={title}
34+
fill
35+
priority
36+
className="object-cover"
37+
/>
38+
39+
{/* Overlay */}
40+
<div className="absolute inset-0 bg-black/60"></div>
41+
42+
{/* Content */}
43+
<div
44+
className={cn(
45+
"absolute bottom-12 left-2 z-10",
46+
"flex flex-col justify-center text-center px-4 pb-2"
47+
)}
48+
>
49+
<h2 className="text-white text-xl md:text-2xl font-bold mb-4">
50+
{title}
51+
</h2>
52+
53+
<div className="flex items-center gap-3 text-gray-300">
54+
<div className="flex items-center gap-2">
55+
<Image
56+
src={authorAvatar || userImg}
57+
alt={title}
58+
priority
59+
className="w-5 h-5 rounded-full"
60+
/>
61+
<p className="text-sm font-medium">{authorName}</p>
62+
</div>
63+
<div className="flex gap-2">
64+
<p className="flex items-center gap-2 text-xs">
65+
<span>
66+
<CalendarRange className="w-5 h-5" />
67+
</span>
68+
{date}
69+
</p>
70+
<p className="flex items-center gap-2 text-xs">
71+
<span>
72+
<Clock3 className="w-5 h-5" />
73+
</span>
74+
{readTime} read
75+
</p>
76+
</div>
77+
</div>
78+
</div>
79+
</div>
80+
);
81+
};
82+
83+
export default DetailHero;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { cn } from "@/lib/utils";
2+
import Image from "next/image";
3+
import React from "react";
4+
5+
type Props = {
6+
title: string;
7+
description: string;
8+
imageSrc: string;
9+
views: number;
10+
className?: string;
11+
};
12+
13+
const PopularCard = ({
14+
title,
15+
description,
16+
imageSrc,
17+
views,
18+
className,
19+
}: Props) => {
20+
return (
21+
<div
22+
className={cn(
23+
"flex gap-2 rounded overflow-hidden shadow-lg bg-white h-32",
24+
className
25+
)}
26+
>
27+
<div className="relative w-42 h-full">
28+
<Image
29+
src={imageSrc}
30+
alt={title}
31+
layout="fill"
32+
objectFit="cover"
33+
className="rounded-t cursor-pointer"
34+
/>
35+
</div>
36+
<div className="flex flex-col py-2">
37+
<div className="font-bold text-richBlack">{title}</div>
38+
<p className="text-richBlack">{description.slice(0, 35)}</p>
39+
{views && (
40+
<div className="text-sm text-deepTeal italic h-full flex items-end">
41+
Views: {views}
42+
</div>
43+
)}
44+
</div>
45+
</div>
46+
);
47+
};
48+
49+
export default PopularCard;

components/blog/details/index.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React from "react";
2+
import { BlogItem } from "@/types/blog";
3+
import DetailHero from "./DetailHero";
4+
import DetailContent from "./DetailContent";
5+
6+
const BlogDeatails = ({ image, title, content, author }: BlogItem) => {
7+
return (
8+
<div className="flex flex-col">
9+
<DetailHero
10+
imageSrc={image}
11+
title={title}
12+
authorName={author.name}
13+
authorAvatar={author.avatar}
14+
date={author.date}
15+
readTime={author.readTime}
16+
className=""
17+
/>
18+
19+
<DetailContent content={content} />
20+
</div>
21+
);
22+
};
23+
24+
export default BlogDeatails;

0 commit comments

Comments
 (0)