Skip to content

Commit 145ed98

Browse files
feat(Blog) (#222)
* feat(Blog):fix for clickable link in post details, fix for author details * feat(Blog):refactoring after removing author modal * feat(Blog): fix unified date format * feat(Blog): Fix for click-outside-to-close search, recommended posts are limited to 3 * feat(Blog): selectedAuthorData fixed * feat(Blog): Added description for /blog/[slug] metadata, Added Schema.org JSON‑LD for Article (BlogPosting) and BreadcrumbList , Added <time datetime> tags where blog dates renders * feat(Blog): fix hover social links, fixed duplication not found search * feat(Blog): Added: breadcrumbs to the post details page and updated the BreadcrumbList, logo to the cocial links in User info, Fixed: main container alignment, category navigation in breadcrumbs * feat(Blog): Added: breadcrumbs to the post details page and updated the BreadcrumbList, logo to the cocial links in User info, Fixed: main container alignment, category navigation in breadcrumbs * feat(Blog): Added scroll on the main blog page on filtering by author, fied breadcrumbs category translaion, added category to the recommended cards, fixed search for localisations * feat(Blog): Changed image size on the post details page * feat(Blog): added tests * feat(Blog): fix for big post on the post page, added tests * feat(Blog): resolving comments * feat(Blog): fixed hover for social links icins - dark theme * feat(Blog): bringing the style on the blog page to a single site style * feat(blog): aligning syles * feat(blog): resolving comment from CodeRabbit * feat(blog):fix comment for deployment
1 parent d02efdd commit 145ed98

6 files changed

Lines changed: 170 additions & 98 deletions

File tree

frontend/app/[locale]/blog/[slug]/PostDetails.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getTranslations } from 'next-intl/server';
55
import { client } from '@/client';
66
import { Link } from '@/i18n/routing';
77
import { formatBlogDate } from '@/lib/blog/date';
8+
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';
89

910
export const revalidate = 0;
1011

@@ -289,7 +290,8 @@ export default async function PostDetails({
289290
: null;
290291

291292
return (
292-
<main className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
293+
<DynamicGridBackground className="bg-gray-50 transition-colors duration-300 dark:bg-transparent py-10">
294+
<main className="relative z-10 mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
293295
{breadcrumbsJsonLd && (
294296
<script
295297
type="application/ld+json"
@@ -501,7 +503,8 @@ export default async function PostDetails({
501503
{post.resourceLink && null}
502504

503505
{(authorBio || authorName || authorMeta) && null}
504-
</main>
506+
</main>
507+
</DynamicGridBackground>
505508
);
506509
}
507510

frontend/app/[locale]/blog/category/[category]/page.tsx

Lines changed: 82 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { client } from '@/client';
66
import { Link } from '@/i18n/routing';
77
import { BlogCategoryGrid } from '@/components/blog/BlogCategoryGrid';
88
import { formatBlogDate } from '@/lib/blog/date';
9+
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';
10+
import { FeaturedPostCtaButton } from '@/components/blog/FeaturedPostCtaButton';
911

1012
export const revalidate = 0;
1113

@@ -85,89 +87,89 @@ export default async function BlogCategoryPage({
8587
const featuredDate = formatBlogDate(featuredPost?.publishedAt);
8688

8789
return (
88-
<main className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
89-
<nav className="mb-6" aria-label="Breadcrumb">
90-
<ol className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
91-
<li className="flex items-center gap-2">
92-
<Link
93-
href="/blog"
94-
className="transition hover:text-[var(--accent-primary)] hover:underline underline-offset-4"
95-
>
96-
{tNav('blog')}
97-
</Link>
98-
<span>&gt;</span>
99-
</li>
100-
<li className="flex items-center gap-2">
101-
<span className="text-[var(--accent-primary)]" aria-current="page">
102-
{categoryDisplay}
103-
</span>
104-
</li>
105-
</ol>
106-
</nav>
107-
<h1 className="text-4xl font-bold mb-4 text-center">
108-
{categoryDisplay}
109-
</h1>
110-
{featuredPost?.mainImage && (
111-
<section className="mt-10">
112-
<article className="group relative overflow-hidden rounded-3xl bg-white dark:bg-black">
113-
<div className="h-[320px] w-full overflow-hidden sm:h-[380px] md:h-[450px] lg:h-[618px] max-h-[65vh]">
114-
<Image
115-
src={featuredPost.mainImage}
116-
alt={featuredPost.title}
117-
width={1400}
118-
height={800}
119-
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
120-
priority={false}
121-
/>
122-
</div>
123-
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-56 bg-gradient-to-t from-white/95 via-white/70 to-transparent dark:from-black/90 dark:via-black/60 sm:h-64" />
124-
<div className="absolute inset-x-0 bottom-0 p-6 sm:p-8">
125-
{featuredPost.categories?.[0] && (
126-
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
127-
{featuredPost.categories[0]}
128-
</div>
129-
)}
130-
<h2 className="mt-2 text-3xl font-semibold text-gray-900 transition group-hover:text-[var(--accent-primary)] dark:text-white dark:group-hover:text-[var(--accent-primary)] sm:text-4xl">
131-
{featuredPost.title}
132-
</h2>
133-
<div className="mt-3 flex items-center gap-3 text-sm text-gray-800 dark:text-gray-200">
134-
{featuredPost.author?.image && (
135-
<Image
136-
src={featuredPost.author.image}
137-
alt={featuredPost.author.name || 'Author'}
138-
width={28}
139-
height={28}
140-
className="h-7 w-7 rounded-full object-cover"
141-
/>
142-
)}
143-
{featuredPost.author?.name && (
144-
<span>{featuredPost.author.name}</span>
145-
)}
146-
{featuredPost.author?.name && featuredDate && <span>·</span>}
147-
{featuredDate && featuredPost.publishedAt && (
148-
<time dateTime={featuredPost.publishedAt}>
149-
{featuredDate}
150-
</time>
151-
)}
152-
</div>
90+
<DynamicGridBackground className="bg-gray-50 transition-colors duration-300 dark:bg-transparent py-10">
91+
<main className="relative z-10 mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
92+
<nav className="mb-6" aria-label="Breadcrumb">
93+
<ol className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
94+
<li className="flex items-center gap-2">
15395
<Link
154-
href={`/blog/${featuredPost.slug.current}`}
155-
className="absolute bottom-6 right-6 inline-flex h-11 w-11 items-center justify-center rounded-full bg-[var(--accent-primary)] text-white opacity-0 transition group-hover:opacity-100 hover:brightness-110"
156-
aria-label={featuredPost.title}
96+
href="/blog"
97+
className="transition hover:text-[var(--accent-primary)] hover:underline underline-offset-4"
15798
>
158-
<span aria-hidden="true"></span>
99+
{tNav('blog')}
159100
</Link>
160-
</div>
161-
</article>
162-
</section>
163-
)}
164-
<div className="mt-12">
165-
<BlogCategoryGrid posts={restPosts} />
166-
</div>
167-
{!posts.length && (
168-
<p className="text-center text-gray-500 mt-10">{t('noPosts')}</p>
169-
)}
170-
</main>
101+
<span>&gt;</span>
102+
</li>
103+
<li className="flex items-center gap-2">
104+
<span className="text-[var(--accent-primary)]" aria-current="page">
105+
{categoryDisplay}
106+
</span>
107+
</li>
108+
</ol>
109+
</nav>
110+
<h1 className="text-4xl font-bold mb-4 text-left">
111+
{categoryDisplay}
112+
</h1>
113+
{featuredPost?.mainImage && (
114+
<section className="mt-10">
115+
<article className="group relative overflow-hidden rounded-3xl bg-white dark:bg-black">
116+
<div className="h-[320px] w-full overflow-hidden sm:h-[380px] md:h-[450px] lg:h-[618px] max-h-[65vh]">
117+
<Image
118+
src={featuredPost.mainImage}
119+
alt={featuredPost.title}
120+
width={1400}
121+
height={800}
122+
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
123+
priority={false}
124+
/>
125+
</div>
126+
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-56 bg-gradient-to-t from-white/95 via-white/70 to-transparent dark:from-black/90 dark:via-black/60 sm:h-64" />
127+
<div className="absolute inset-x-0 bottom-0 p-6 sm:p-8">
128+
{featuredPost.categories?.[0] && (
129+
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
130+
{featuredPost.categories[0]}
131+
</div>
132+
)}
133+
<h2 className="mt-2 text-3xl font-semibold text-gray-900 transition group-hover:text-[var(--accent-primary)] dark:text-white dark:group-hover:text-[var(--accent-primary)] sm:text-4xl">
134+
{featuredPost.title}
135+
</h2>
136+
<div className="mt-3 flex items-center gap-3 text-sm text-gray-800 dark:text-gray-200">
137+
{featuredPost.author?.image && (
138+
<Image
139+
src={featuredPost.author.image}
140+
alt={featuredPost.author.name || 'Author'}
141+
width={28}
142+
height={28}
143+
className="h-7 w-7 rounded-full object-cover"
144+
/>
145+
)}
146+
{featuredPost.author?.name && (
147+
<span>{featuredPost.author.name}</span>
148+
)}
149+
{featuredPost.author?.name && featuredDate && <span>·</span>}
150+
{featuredDate && featuredPost.publishedAt && (
151+
<time dateTime={featuredPost.publishedAt}>
152+
{featuredDate}
153+
</time>
154+
)}
155+
</div>
156+
</div>
157+
<FeaturedPostCtaButton
158+
href={`/blog/${featuredPost.slug.current}`}
159+
label={featuredPost.title || 'Read more'}
160+
className="!absolute !bottom-6 !right-6 z-10 h-11 w-11 rounded-full bg-[var(--accent-primary)] text-white opacity-0 shadow-sm transition group-hover:opacity-100 focus-visible:opacity-100 group-focus-within:opacity-100"
161+
/>
162+
</article>
163+
</section>
164+
)}
165+
<div className="mt-12">
166+
<BlogCategoryGrid posts={restPosts} />
167+
</div>
168+
{!posts.length && (
169+
<p className="text-center text-gray-500 mt-10">{t('noPosts')}</p>
170+
)}
171+
</main>
172+
</DynamicGridBackground>
171173
);
172174
}
173175

frontend/app/[locale]/blog/page.tsx

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { unstable_noStore as noStore } from 'next/cache';
33
import { getTranslations } from 'next-intl/server';
44
import { client } from '@/client';
55
import BlogFilters from '@/components/blog/BlogFilters';
6+
import { BlogPageHeader } from '@/components/blog/BlogPageHeader';
7+
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';
68

79
export const revalidate = 0;
810

@@ -22,12 +24,18 @@ export async function generateMetadata({
2224

2325
export default async function BlogPage({
2426
params,
27+
searchParams,
2528
}: {
2629
params: Promise<{ locale: string }>;
30+
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
2731
}) {
2832
noStore();
2933
const { locale } = await params;
3034
const t = await getTranslations({ locale, namespace: 'blog' });
35+
const sp = searchParams ? await searchParams : undefined;
36+
const authorParam =
37+
typeof sp?.author === 'string' ? sp.author.trim() : '';
38+
const hasAuthorFilter = authorParam.length > 0;
3139

3240
const posts = await client.withConfig({ useCdn: false }).fetch(
3341
groq`
@@ -77,16 +85,17 @@ export default async function BlogPage({
7785
const featuredPost = posts?.[0];
7886

7987
return (
80-
<main className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
81-
<h1 className="text-4xl font-bold mb-4 text-center">{t('title')}</h1>
82-
<p className="mx-auto max-w-2xl text-center text-base text-gray-500 dark:text-gray-400">
83-
{t('subtitle')}
84-
</p>
85-
<BlogFilters
86-
posts={posts}
87-
categories={categories}
88-
featuredPost={featuredPost}
89-
/>
90-
</main>
88+
<DynamicGridBackground className="bg-gray-50 transition-colors duration-300 dark:bg-transparent py-10">
89+
<main className="relative z-10 mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
90+
{!hasAuthorFilter && (
91+
<BlogPageHeader title={t('title')} subtitle={t('subtitle')} />
92+
)}
93+
<BlogFilters
94+
posts={posts}
95+
categories={categories}
96+
featuredPost={featuredPost}
97+
/>
98+
</main>
99+
</DynamicGridBackground>
91100
);
92101
}

frontend/components/blog/BlogFilters.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,16 @@ export default function BlogFilters({
194194
const name = author.name || '';
195195
const norm = normalizeAuthor(name);
196196
if (!norm) return;
197-
setSelectedAuthor(prev =>
198-
prev?.norm === norm ? null : { name, norm, data: author }
199-
);
197+
const isSame = selectedAuthor?.norm === norm;
198+
setSelectedAuthor(isSame ? null : { name, norm, data: author });
199+
const params = new URLSearchParams(searchParams?.toString() || '');
200+
if (isSame) {
201+
params.delete('author');
202+
} else {
203+
params.set('author', name);
204+
}
205+
const nextPath = params.toString() ? `${pathname}?${params}` : pathname;
206+
router.replace(nextPath);
200207
};
201208

202209
const clearAll = () => {
@@ -537,7 +544,7 @@ export default function BlogFilters({
537544
className={
538545
!resolvedCategory
539546
? 'rounded-full border border-transparent px-4 py-2 text-sm font-medium text-[var(--accent-primary)] transition whitespace-nowrap sm:border-[var(--accent-primary)]'
540-
: 'rounded-full border border-transparent px-4 py-2 text-sm text-gray-600 hover:text-[var(--accent-primary)] transition whitespace-nowrap dark:text-gray-300 sm:border-gray-300 sm:text-gray-700 sm:dark:border-gray-700 sm:dark:text-gray-200'
547+
: 'rounded-full border border-transparent px-4 py-2 text-sm text-gray-600 transition whitespace-nowrap dark:text-gray-300 sm:border-gray-300 sm:text-gray-700 sm:dark:border-gray-700 sm:dark:text-gray-200 hover:bg-secondary hover:text-foreground'
541548
}
542549
>
543550
{t('all')}
@@ -556,7 +563,7 @@ export default function BlogFilters({
556563
className={
557564
resolvedCategory?.norm === category.norm
558565
? 'rounded-full border border-transparent px-4 py-2 text-sm font-medium text-[var(--accent-primary)] transition whitespace-nowrap sm:border-[var(--accent-primary)]'
559-
: 'rounded-full border border-transparent px-4 py-2 text-sm text-gray-600 hover:text-[var(--accent-primary)] transition whitespace-nowrap dark:text-gray-300 sm:border-gray-300 sm:text-gray-700 sm:dark:border-gray-700 sm:dark:text-gray-200'
566+
: 'rounded-full border border-transparent px-4 py-2 text-sm text-gray-600 transition whitespace-nowrap dark:text-gray-300 sm:border-gray-300 sm:text-gray-700 sm:dark:border-gray-700 sm:dark:text-gray-200 hover:bg-secondary hover:text-foreground'
560567
}
561568
>
562569
{getCategoryLabel(category.name)}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use client';
2+
3+
import { useSearchParams } from 'next/navigation';
4+
5+
type BlogPageHeaderProps = {
6+
title: string;
7+
subtitle: string;
8+
};
9+
10+
export function BlogPageHeader({ title, subtitle }: BlogPageHeaderProps) {
11+
const searchParams = useSearchParams();
12+
const authorParam = (searchParams?.get('author') || '').trim();
13+
if (authorParam) return null;
14+
15+
return (
16+
<>
17+
<h1 className="text-5xl font-extrabold mb-3 text-center leading-[1.1] bg-gradient-to-b from-[color-mix(in_srgb,var(--accent-primary)_70%,white)] to-[var(--accent-hover)] bg-clip-text text-transparent">
18+
{title}
19+
</h1>
20+
<p className="mx-auto mb-10 max-w-2xl text-center text-base text-gray-500 dark:text-gray-400">
21+
{subtitle}
22+
</p>
23+
</>
24+
);
25+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use client';
2+
3+
import { ArrowUpRight } from 'lucide-react';
4+
import { HeaderButton } from '@/components/shared/HeaderButton';
5+
6+
type FeaturedPostCtaButtonProps = {
7+
href: string;
8+
label: string;
9+
className?: string;
10+
};
11+
12+
export function FeaturedPostCtaButton({
13+
href,
14+
label,
15+
className,
16+
}: FeaturedPostCtaButtonProps) {
17+
return (
18+
<HeaderButton
19+
href={href}
20+
variant="icon"
21+
icon={ArrowUpRight}
22+
label={label}
23+
className={className}
24+
/>
25+
);
26+
}

0 commit comments

Comments
 (0)