Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
725d234
feat(Blog):fix for clickable link in post details, fix for author det…
KomrakovaAnna Jan 24, 2026
6e8561d
feat(Blog):refactoring after removing author modal
KomrakovaAnna Jan 24, 2026
0334ea5
feat(Blog):mergin develop
KomrakovaAnna Jan 24, 2026
7bf3768
feat(Blog): fix unified date format
KomrakovaAnna Jan 24, 2026
d60fbe8
feat(Blog): Fix for click-outside-to-close search, recommended posts…
KomrakovaAnna Jan 25, 2026
c836b11
merging develop
KomrakovaAnna Jan 25, 2026
705f075
feat(Blog): selectedAuthorData fixed
KomrakovaAnna Jan 25, 2026
69a8190
feat(Blog): Added description for /blog/[slug] metadata, Added Schema…
KomrakovaAnna Jan 25, 2026
f628c8c
feat(Blog): fix hover social links, fixed duplication not found search
KomrakovaAnna Jan 25, 2026
a2fa0b6
Merge branch 'develop' into sanity
KomrakovaAnna Jan 25, 2026
42d1a62
feat(Blog): Added: breadcrumbs to the post details page and updated t…
KomrakovaAnna Jan 25, 2026
faab3bd
feat(Blog): Added: breadcrumbs to the post details page and updated t…
KomrakovaAnna Jan 25, 2026
102f427
merging develop
KomrakovaAnna Jan 25, 2026
91f05d8
feat(Blog): Added scroll on the main blog page on filtering by author…
KomrakovaAnna Jan 25, 2026
d73087a
Merge branch 'develop' into sanity
KomrakovaAnna Jan 25, 2026
9347874
feat(Blog): Changed image size on the post details page
KomrakovaAnna Jan 25, 2026
2591e67
Merge branch 'develop' into sanity
KomrakovaAnna Jan 25, 2026
a04e8bb
feat(Blog): added tests
KomrakovaAnna Jan 26, 2026
83d54a6
added package-lock
KomrakovaAnna Jan 26, 2026
f0a1419
feat(Blog): fix for big post on the post page, added tests
KomrakovaAnna Jan 26, 2026
fd582c2
feat(Blog): resolving comments
KomrakovaAnna Jan 26, 2026
f0144a3
Merge branch 'develop' into sanity
KomrakovaAnna Jan 26, 2026
556e400
feat(Blog): fixed hover for social links icins - dark theme
KomrakovaAnna Jan 26, 2026
7e54349
Merge branch 'develop' into sanity
KomrakovaAnna Jan 26, 2026
177a77e
feat(Blog): bringing the style on the blog page to a single site style
KomrakovaAnna Jan 27, 2026
ded4abf
Merge branch 'develop' into sanity
KomrakovaAnna Jan 27, 2026
9faffa5
feat(blog): aligning syles
KomrakovaAnna Jan 27, 2026
7299946
feat(blog): resolving comment from CodeRabbit
KomrakovaAnna Jan 27, 2026
1f293ac
feat(blog):fix comment for deployment
KomrakovaAnna Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions frontend/app/[locale]/blog/[slug]/PostDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getTranslations } from 'next-intl/server';
import { client } from '@/client';
import { Link } from '@/i18n/routing';
import { formatBlogDate } from '@/lib/blog/date';
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';

export const revalidate = 0;

Expand Down Expand Up @@ -289,7 +290,8 @@ export default async function PostDetails({
: null;

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

{(authorBio || authorName || authorMeta) && null}
</main>
</main>
</DynamicGridBackground>
);
}

Expand Down
162 changes: 82 additions & 80 deletions frontend/app/[locale]/blog/category/[category]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { client } from '@/client';
import { Link } from '@/i18n/routing';
import { BlogCategoryGrid } from '@/components/blog/BlogCategoryGrid';
import { formatBlogDate } from '@/lib/blog/date';
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';
import { FeaturedPostCtaButton } from '@/components/blog/FeaturedPostCtaButton';

export const revalidate = 0;

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

return (
<main className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<nav className="mb-6" aria-label="Breadcrumb">
<ol className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
<li className="flex items-center gap-2">
<Link
href="/blog"
className="transition hover:text-[var(--accent-primary)] hover:underline underline-offset-4"
>
{tNav('blog')}
</Link>
<span>&gt;</span>
</li>
<li className="flex items-center gap-2">
<span className="text-[var(--accent-primary)]" aria-current="page">
{categoryDisplay}
</span>
</li>
</ol>
</nav>
<h1 className="text-4xl font-bold mb-4 text-center">
{categoryDisplay}
</h1>
{featuredPost?.mainImage && (
<section className="mt-10">
<article className="group relative overflow-hidden rounded-3xl bg-white dark:bg-black">
<div className="h-[320px] w-full overflow-hidden sm:h-[380px] md:h-[450px] lg:h-[618px] max-h-[65vh]">
<Image
src={featuredPost.mainImage}
alt={featuredPost.title}
width={1400}
height={800}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
priority={false}
/>
</div>
<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" />
<div className="absolute inset-x-0 bottom-0 p-6 sm:p-8">
{featuredPost.categories?.[0] && (
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
{featuredPost.categories[0]}
</div>
)}
<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">
{featuredPost.title}
</h2>
<div className="mt-3 flex items-center gap-3 text-sm text-gray-800 dark:text-gray-200">
{featuredPost.author?.image && (
<Image
src={featuredPost.author.image}
alt={featuredPost.author.name || 'Author'}
width={28}
height={28}
className="h-7 w-7 rounded-full object-cover"
/>
)}
{featuredPost.author?.name && (
<span>{featuredPost.author.name}</span>
)}
{featuredPost.author?.name && featuredDate && <span>·</span>}
{featuredDate && featuredPost.publishedAt && (
<time dateTime={featuredPost.publishedAt}>
{featuredDate}
</time>
)}
</div>
<DynamicGridBackground className="bg-gray-50 transition-colors duration-300 dark:bg-transparent py-10">
<main className="relative z-10 mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<nav className="mb-6" aria-label="Breadcrumb">
<ol className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
<li className="flex items-center gap-2">
<Link
href={`/blog/${featuredPost.slug.current}`}
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"
aria-label={featuredPost.title}
href="/blog"
className="transition hover:text-[var(--accent-primary)] hover:underline underline-offset-4"
>
<span aria-hidden="true">↗</span>
{tNav('blog')}
</Link>
</div>
</article>
</section>
)}
<div className="mt-12">
<BlogCategoryGrid posts={restPosts} />
</div>
{!posts.length && (
<p className="text-center text-gray-500 mt-10">{t('noPosts')}</p>
)}
</main>
<span>&gt;</span>
</li>
<li className="flex items-center gap-2">
<span className="text-[var(--accent-primary)]" aria-current="page">
{categoryDisplay}
</span>
</li>
</ol>
</nav>
<h1 className="text-4xl font-bold mb-4 text-left">
{categoryDisplay}
</h1>
{featuredPost?.mainImage && (
<section className="mt-10">
<article className="group relative overflow-hidden rounded-3xl bg-white dark:bg-black">
<div className="h-[320px] w-full overflow-hidden sm:h-[380px] md:h-[450px] lg:h-[618px] max-h-[65vh]">
<Image
src={featuredPost.mainImage}
alt={featuredPost.title}
width={1400}
height={800}
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
priority={false}
/>
</div>
<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" />
<div className="absolute inset-x-0 bottom-0 p-6 sm:p-8">
{featuredPost.categories?.[0] && (
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
{featuredPost.categories[0]}
</div>
)}
<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">
{featuredPost.title}
</h2>
<div className="mt-3 flex items-center gap-3 text-sm text-gray-800 dark:text-gray-200">
{featuredPost.author?.image && (
<Image
src={featuredPost.author.image}
alt={featuredPost.author.name || 'Author'}
width={28}
height={28}
className="h-7 w-7 rounded-full object-cover"
/>
)}
{featuredPost.author?.name && (
<span>{featuredPost.author.name}</span>
)}
{featuredPost.author?.name && featuredDate && <span>·</span>}
{featuredDate && featuredPost.publishedAt && (
<time dateTime={featuredPost.publishedAt}>
{featuredDate}
</time>
)}
</div>
</div>
<FeaturedPostCtaButton
href={`/blog/${featuredPost.slug.current}`}
label={featuredPost.title || 'Read more'}
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"
/>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</article>
</section>
)}
<div className="mt-12">
<BlogCategoryGrid posts={restPosts} />
</div>
{!posts.length && (
<p className="text-center text-gray-500 mt-10">{t('noPosts')}</p>
)}
</main>
</DynamicGridBackground>
);
}

Expand Down
31 changes: 20 additions & 11 deletions frontend/app/[locale]/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { unstable_noStore as noStore } from 'next/cache';
import { getTranslations } from 'next-intl/server';
import { client } from '@/client';
import BlogFilters from '@/components/blog/BlogFilters';
import { BlogPageHeader } from '@/components/blog/BlogPageHeader';
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';

export const revalidate = 0;

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

export default async function BlogPage({
params,
searchParams,
}: {
params: Promise<{ locale: string }>;
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
noStore();
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'blog' });
const sp = searchParams ? await searchParams : undefined;
const authorParam =
typeof sp?.author === 'string' ? sp.author.trim() : '';
const hasAuthorFilter = authorParam.length > 0;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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

return (
<main className="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<h1 className="text-4xl font-bold mb-4 text-center">{t('title')}</h1>
<p className="mx-auto max-w-2xl text-center text-base text-gray-500 dark:text-gray-400">
{t('subtitle')}
</p>
<BlogFilters
posts={posts}
categories={categories}
featuredPost={featuredPost}
/>
</main>
<DynamicGridBackground className="bg-gray-50 transition-colors duration-300 dark:bg-transparent py-10">
<main className="relative z-10 mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
{!hasAuthorFilter && (
<BlogPageHeader title={t('title')} subtitle={t('subtitle')} />
)}
<BlogFilters
posts={posts}
categories={categories}
featuredPost={featuredPost}
/>
</main>
</DynamicGridBackground>
);
}
17 changes: 12 additions & 5 deletions frontend/components/blog/BlogFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,16 @@ export default function BlogFilters({
const name = author.name || '';
const norm = normalizeAuthor(name);
if (!norm) return;
setSelectedAuthor(prev =>
prev?.norm === norm ? null : { name, norm, data: author }
);
const isSame = selectedAuthor?.norm === norm;
setSelectedAuthor(isSame ? null : { name, norm, data: author });
const params = new URLSearchParams(searchParams?.toString() || '');
if (isSame) {
params.delete('author');
} else {
params.set('author', name);
}
const nextPath = params.toString() ? `${pathname}?${params}` : pathname;
router.replace(nextPath);
};

const clearAll = () => {
Expand Down Expand Up @@ -537,7 +544,7 @@ export default function BlogFilters({
className={
!resolvedCategory
? '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)]'
: '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'
: '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'
}
>
{t('all')}
Expand All @@ -556,7 +563,7 @@ export default function BlogFilters({
className={
resolvedCategory?.norm === category.norm
? '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)]'
: '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'
: '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'
}
>
{getCategoryLabel(category.name)}
Expand Down
25 changes: 25 additions & 0 deletions frontend/components/blog/BlogPageHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

import { useSearchParams } from 'next/navigation';

type BlogPageHeaderProps = {
title: string;
subtitle: string;
};

export function BlogPageHeader({ title, subtitle }: BlogPageHeaderProps) {
const searchParams = useSearchParams();
const authorParam = (searchParams?.get('author') || '').trim();
if (authorParam) return null;

return (
<>
<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">
{title}
</h1>
<p className="mx-auto mb-10 max-w-2xl text-center text-base text-gray-500 dark:text-gray-400">
{subtitle}
</p>
</>
);
}
26 changes: 26 additions & 0 deletions frontend/components/blog/FeaturedPostCtaButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client';

import { ArrowUpRight } from 'lucide-react';
import { HeaderButton } from '@/components/shared/HeaderButton';

type FeaturedPostCtaButtonProps = {
href: string;
label: string;
className?: string;
};

export function FeaturedPostCtaButton({
href,
label,
className,
}: FeaturedPostCtaButtonProps) {
return (
<HeaderButton
href={href}
variant="icon"
icon={ArrowUpRight}
label={label}
className={className}
/>
);
}