Skip to content

Commit c36c403

Browse files
ViktorSvertokaliudmylasovetovsKomrakovaAnnaTiZoriiYNazymko12
authored
Release v0.5.3 (#268)
* chore: bump Node.js to 20 for Netlify * feat(md) add netlify status (#234) * (SP 2) [Shop UI] Unify storefront styles across components and interactions (#236) * Host (#237) * feat(md) add netlify status * feat(files): add packages * fix(auth): use currentTarget for email input validity * Host (#238) * feat(md) add netlify status * feat(files): add packages * fix(auth): use currentTarget for email input validity * fix(auth): use currentTarget for email input validity * fix(auth): use currentTarget in password field * (SP 1) [Shop UI] Add page metadata across shop routes (#239) * (SP 2) [Shop UI] Unify storefront styles across components and interactions * (SP 1) [Shop UI] Add page metadata across shop routes * (SP: 3) [Cache] Add Upstash Redis cache for Q&A (#241) * feat(md) add netlify status * feat(files): add packages * fix(auth): use currentTarget for email input validity * fix(auth): use currentTarget for email input validity * fix(auth): use currentTarget in password field * feat(qa): add Redis cache layer for Q&A * Fix Q&A Redis cache parsing for Upstash REST (#243) * feat(md) add netlify status * feat(files): add packages * fix(auth): use currentTarget for email input validity * fix(auth): use currentTarget for email input validity * fix(auth): use currentTarget in password field * feat(qa): add Redis cache layer for Q&A * fix(qa): handle non-string Redis cache values * feat(Blog):Adding pagination (#244) * 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 * feat(Blog): adding pagination * feat(Blog): Addind Text formatting visibility * (SP:2) feat(api): clean up AI helper for Vercel & fix orders i18n (#245) - Refactor /api/ai/explain route for Vercel deployment - Replace dynamic import with static import of groq-sdk - Use request.json() instead of Netlify-safe body parsing - Add proper error handling with Groq.APIError types - Simplify GET health check endpoint - Update model from llama3-70b-8192 to llama-3.3-70b-versatile - Add table.openOrder and table.orderId to en/uk/pl locales * refactor(home): update button, cards, and online counter UI (#248) Improve primary button styles Fix card overflow and add subtle highlights Redesign online users counter and reduce font size * fix(api): enforce rate limiting (#246) * (SP:2) feat(api): clean up AI helper for Vercel & fix orders i18n - Refactor /api/ai/explain route for Vercel deployment - Replace dynamic import with static import of groq-sdk - Use request.json() instead of Netlify-safe body parsing - Add proper error handling with Groq.APIError types - Simplify GET health check endpoint - Update model from llama3-70b-8192 to llama-3.3-70b-versatile - Add table.openOrder and table.orderId to en/uk/pl locales * fix(api): enforce rate limiting --------- Co-authored-by: Viktor Svertoka <victor.svertoka@gmail.com> * feat(Blog):formating text (#249) * 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 * feat(Blog): adding pagination * feat(Blog): Addind Text formatting visibility * feat(Blog):adding text formating fix * ref(files): refactoring code & bag fix (#250) * chore(release): v0.5.2 * Lso/feat/shop design (#252) * feat(i18n): add translations for blog categories, and UI components (#253) - Translate blog category labels in mobile menu, cards, and filters - Add CTA hover variants translations - Add aria-label translations for theme toggle, cart, search, GitHub star - Update translation files for EN, UK, PL locales * feat blog: fix for paddings on mobile (#254) * 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 * feat(Blog): adding pagination * feat(Blog): Addind Text formatting visibility * feat(Blog):adding text formating fix * chore(lint): finalize ESLint + Prettier (#256) * feat(leaderboard): finalize components and fix lint errors (#259) * (SP 1) [FIX] set up eslint/prettier + stabilize formatting workflow (own files only) (#264) * (SP 2) [Shop UI] Unify storefront styles across components and interactions * (SP 1) [Shop UI] Add page metadata across shop routes * (SP 1) [FIX] names of components, replacing tests, clean code * (SP 1) [FIX] remove magic constant and align restock sweep test typing * (SP 1) [FIX] remove duplicate * (SP 1) [FIX] set up eslint/prettier + stabilize formatting workflow (own files only) * (SP 1) [FIX] Tailwind hints * (SP 1) [FIX] Tailwind hints revert * (SP: 5) [Quiz] Redis caching + guest session fix + cleanup (#263) * feat(quiz-ui): quiz UI polish - tabs, category accents, color scheme (issues #181, #193, #194) - Refactor QaTabButton to shared CategoryTabButton component - Add category accent colors to QuizCard, buttons, progress indicators - Standardize colors with CSS variables, traffic light timer - Add DynamicGridBackground to quizzes list page - Border-only answer feedback, semi-transparent progress styles * docs: update .gitignore * fix(quiz): align disqualification threshold with warning banner Changed violationsCount > 3 to >= 3 in QuizResult points block to match the warning banner threshold at line 124. * feat(quiz-testing): add quiz unit tests - Configure Vitest for quiz module - Add test factories and setup utilities - Add quiz-crypto tests (13 tests) - Add quiz-session tests (12 tests) * test(quiz): add integration tests for verify-answer API and useAntiCheat hook (#199) - verify-answer.test.ts: 8 tests for API endpoint - Correct/wrong answer verification - Validation errors (missing fields, tampered data) - Security: rejects modified encrypted answers - quiz-anticheat.test.ts: 10 tests for useAntiCheat hook - Detects copy, paste, context-menu, tab-switch events - Respects isActive flag - Reset and cleanup functionality Total quiz tests: 52 (9 setup + 25 unit + 18 integration) * test(quiz): expand test coverage to 90%+ with hooks, API routes, and UI flow Add 28 new tests covering: - useQuizSession hook (6 tests) - useQuizGuards hook (8 tests) - guest-quiz storage (5 tests) - guest-result API route (5 tests) - quiz-slug API route (3 tests) - QuizContainer UI flow (1 test) Coverage: 35% -> 90.94% (quiz scope) Tests: 52 -> 80 * chore: remove coverage-quiz from git, add to .gitignore * chore: add coverage-quiz to .gitignore, fix quiz guards test * fix(a11y): improve quiz accessibility and i18n compliance * fix(sl/feat/quiz): replace with correct name for react icon * feat(quiz): implement Redis caching + session fixes + cleanup Closes #260, #261, #262 - Add quiz-answers-redis.ts with getOrCreateQuizAnswersCache() - Cache correct answers per quiz (12h TTL) - Replace AES-256-GCM decryption with O(1) Redis lookup - Add initializeQuizCache server action - Update verify-answer route to use Redis - Allow restoring 'completed' sessions (not just 'in_progress') - Only clear session for authenticated users after submit - Guest result screen now survives language switch - Delete PendingResultHandler.tsx (never executes) - Delete start-session/route.ts (broken import, unused) - Delete quiz-crypto.ts (AES replaced by Redis) - Delete quiz-crypto.test.ts (tests dead code) - Rewrite verify-answer.test.ts for Redis API (8 tests) - Fix quiz-session.test.ts for completed session restore * chore(quiz): delete unused start-session route Part of #262 cleanup - route had broken import after Redis migration. * git commit -m "fix(quiz): add NaN seed validation and cache/DB fallback - Validate seed param to prevent NaN breaking question shuffle - Add cache recovery in verify-answer when Redis cache expires - Add DB fallback in getCorrectAnswer when Redis unavailable * chore: remove redis ttl for static quiz and qa caches (#265) * fix(layout): remove duplicate padding from quiz routes (#266) * feat(quiz-ui): quiz UI polish - tabs, category accents, color scheme (issues #181, #193, #194) - Refactor QaTabButton to shared CategoryTabButton component - Add category accent colors to QuizCard, buttons, progress indicators - Standardize colors with CSS variables, traffic light timer - Add DynamicGridBackground to quizzes list page - Border-only answer feedback, semi-transparent progress styles * docs: update .gitignore * fix(quiz): align disqualification threshold with warning banner Changed violationsCount > 3 to >= 3 in QuizResult points block to match the warning banner threshold at line 124. * feat(quiz-testing): add quiz unit tests - Configure Vitest for quiz module - Add test factories and setup utilities - Add quiz-crypto tests (13 tests) - Add quiz-session tests (12 tests) * test(quiz): add integration tests for verify-answer API and useAntiCheat hook (#199) - verify-answer.test.ts: 8 tests for API endpoint - Correct/wrong answer verification - Validation errors (missing fields, tampered data) - Security: rejects modified encrypted answers - quiz-anticheat.test.ts: 10 tests for useAntiCheat hook - Detects copy, paste, context-menu, tab-switch events - Respects isActive flag - Reset and cleanup functionality Total quiz tests: 52 (9 setup + 25 unit + 18 integration) * test(quiz): expand test coverage to 90%+ with hooks, API routes, and UI flow Add 28 new tests covering: - useQuizSession hook (6 tests) - useQuizGuards hook (8 tests) - guest-quiz storage (5 tests) - guest-result API route (5 tests) - quiz-slug API route (3 tests) - QuizContainer UI flow (1 test) Coverage: 35% -> 90.94% (quiz scope) Tests: 52 -> 80 * chore: remove coverage-quiz from git, add to .gitignore * chore: add coverage-quiz to .gitignore, fix quiz guards test * fix(a11y): improve quiz accessibility and i18n compliance * fix(sl/feat/quiz): replace with correct name for react icon * feat(quiz): implement Redis caching + session fixes + cleanup Closes #260, #261, #262 - Add quiz-answers-redis.ts with getOrCreateQuizAnswersCache() - Cache correct answers per quiz (12h TTL) - Replace AES-256-GCM decryption with O(1) Redis lookup - Add initializeQuizCache server action - Update verify-answer route to use Redis - Allow restoring 'completed' sessions (not just 'in_progress') - Only clear session for authenticated users after submit - Guest result screen now survives language switch - Delete PendingResultHandler.tsx (never executes) - Delete start-session/route.ts (broken import, unused) - Delete quiz-crypto.ts (AES replaced by Redis) - Delete quiz-crypto.test.ts (tests dead code) - Rewrite verify-answer.test.ts for Redis API (8 tests) - Fix quiz-session.test.ts for completed session restore * chore(quiz): delete unused start-session route Part of #262 cleanup - route had broken import after Redis migration. * git commit -m "fix(quiz): add NaN seed validation and cache/DB fallback - Validate seed param to prevent NaN breaking question shuffle - Add cache recovery in verify-answer when Redis cache expires - Add DB fallback in getCorrectAnswer when Redis unavailable * fix(layout): remove duplicate padding from quiz routes Add isQuizzesPath to MainSwitcher to exclude /quiz and /quizzes routes from extra px-6 padding, matching Q&A page behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: liudmylasovetovs <127711697+liudmylasovetovs@users.noreply.github.com> Co-authored-by: KomrakovaAnna <komrakova.anna@gmail.com> Co-authored-by: Tetiana Zorii <131365289+TiZorii@users.noreply.github.com> Co-authored-by: Yuliia Nazymko <122815071+YNazymko12@users.noreply.github.com> Co-authored-by: AlinaRyabova <115992255+AlinaRyabova@users.noreply.github.com> Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5fc56af commit c36c403

436 files changed

Lines changed: 9820 additions & 11659 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.prettierrc

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

frontend/.prettierrc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
"trailingComma": "es5",
77
"bracketSpacing": true,
88
"arrowParens": "avoid",
9-
"proseWrap": "always"
9+
"proseWrap": "always",
10+
11+
"plugins": ["prettier-plugin-tailwindcss"],
12+
"tailwindFunctions": ["clsx", "cn"]
1013
}

frontend/README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with
2+
[`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
23

34
## Getting Started
45

@@ -14,23 +15,35 @@ pnpm dev
1415
bun dev
1516
```
1617

17-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the
19+
result.
1820

19-
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
21+
You can start editing the page by modifying `app/page.tsx`. The page
22+
auto-updates as you edit the file.
2023

21-
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
24+
This project uses
25+
[`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts)
26+
to automatically optimize and load [Geist](https://vercel.com/font), a new font
27+
family for Vercel.
2228

2329
## Learn More
2430

2531
To learn more about Next.js, take a look at the following resources:
2632

27-
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
33+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js
34+
features and API.
2835
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
2936

30-
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
37+
You can check out
38+
[the Next.js GitHub repository](https://github.com/vercel/next.js) - your
39+
feedback and contributions are welcome!
3140

3241
## Deploy on Vercel
3342

34-
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
43+
The easiest way to deploy your Next.js app is to use the
44+
[Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme)
45+
from the creators of Next.js.
3546

36-
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
47+
Check out our
48+
[Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying)
49+
for more details.

frontend/actions/quiz.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
'use server';
22

3+
import { eq, inArray } from 'drizzle-orm';
4+
35
import { db } from '@/db';
6+
import { awardQuizPoints, calculateQuizPoints } from '@/db/queries/points';
47
import {
5-
quizAttempts,
6-
quizAttemptAnswers,
78
quizAnswers,
9+
quizAttemptAnswers,
10+
quizAttempts,
811
quizQuestions,
912
} from '@/db/schema/quiz';
10-
import { awardQuizPoints,calculateQuizPoints } from '@/db/queries/points';
1113
import { getCurrentUser } from '@/lib/auth';
12-
import { eq, inArray, and } from 'drizzle-orm';
1314

1415
export interface UserAnswer {
1516
questionId: string;
@@ -93,7 +94,8 @@ export async function submitQuizAttempt(
9394
return { success: false, error: 'Unauthorized' };
9495
}
9596

96-
const { userId, quizId, answers, violations, startedAt, completedAt } = input;
97+
const { userId, quizId, answers, violations, startedAt, completedAt } =
98+
input;
9799

98100
if (userId && userId !== session.id) {
99101
return { success: false, error: 'User mismatch' };
@@ -186,9 +188,10 @@ export async function submitQuizAttempt(
186188
};
187189
}
188190

189-
const percentage = ((correctAnswersCount / questionIds.length) * 100).toFixed(
190-
2
191-
);
191+
const percentage = (
192+
(correctAnswersCount / questionIds.length) *
193+
100
194+
).toFixed(2);
192195
const integrityScore = calculateIntegrityScore(violations);
193196
const timeSpentSeconds = Math.floor(
194197
(completedAtDate.getTime() - startedAtDate.getTime()) / 1000
@@ -252,3 +255,22 @@ export async function submitQuizAttempt(
252255
};
253256
}
254257
}
258+
259+
export async function initializeQuizCache(
260+
quizId: string
261+
): Promise<{ success: boolean; error?: string }> {
262+
try {
263+
const { getOrCreateQuizAnswersCache } =
264+
await import('@/lib/quiz/quiz-answers-redis');
265+
const success = await getOrCreateQuizAnswersCache(quizId);
266+
267+
if (!success) {
268+
return { success: false, error: 'Quiz not found' };
269+
}
270+
271+
return { success: true };
272+
} catch (error) {
273+
console.error('Failed to initialize quiz cache:', error);
274+
return { success: false, error: 'Internal server error' };
275+
}
276+
}
Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,34 @@
1-
import { getTranslations } from "next-intl/server"
2-
import { getPlatformStats } from "@/lib/about/stats"
3-
import { getSponsors } from "@/lib/about/github-sponsors"
1+
import { getTranslations } from 'next-intl/server';
42

5-
import { HeroSection } from "@/components/about/HeroSection"
6-
import { TopicsSection } from "@/components/about/TopicsSection"
7-
import { FeaturesSection } from "@/components/about/FeaturesSection"
8-
import { PricingSection } from "@/components/about/PricingSection"
9-
import { CommunitySection } from "@/components/about/CommunitySection"
3+
import { CommunitySection } from '@/components/about/CommunitySection';
4+
import { FeaturesSection } from '@/components/about/FeaturesSection';
5+
import { HeroSection } from '@/components/about/HeroSection';
6+
import { PricingSection } from '@/components/about/PricingSection';
7+
import { TopicsSection } from '@/components/about/TopicsSection';
8+
import { getSponsors } from '@/lib/about/github-sponsors';
9+
import { getPlatformStats } from '@/lib/about/stats';
1010

1111
export async function generateMetadata() {
12-
const t = await getTranslations("about")
12+
const t = await getTranslations('about');
1313
return {
14-
title: t("metaTitle"),
15-
description: t("metaDescription"),
16-
}
14+
title: t('metaTitle'),
15+
description: t('metaDescription'),
16+
};
1717
}
1818

1919
export default async function AboutPage() {
2020
const [stats, sponsors] = await Promise.all([
2121
getPlatformStats(),
22-
getSponsors()
23-
])
22+
getSponsors(),
23+
]);
2424

2525
return (
26-
<main className="min-h-screen bg-gray-50 dark:bg-black overflow-hidden text-gray-900 dark:text-white
27-
w-[100vw] relative left-[50%] right-[50%] -ml-[50vw] -mr-[50vw]"
28-
>
29-
26+
<main className="relative right-[50%] left-[50%] -mr-[50vw] -ml-[50vw] min-h-screen w-[100vw] overflow-hidden bg-gray-50 text-gray-900 dark:bg-black dark:text-white">
3027
<HeroSection stats={stats} />
3128
<TopicsSection />
3229
<FeaturesSection />
3330
<PricingSection sponsors={sponsors} />
3431
<CommunitySection />
35-
3632
</main>
37-
)
38-
}
33+
);
34+
}

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import groq from 'groq';
12
import Image from 'next/image';
23
import { notFound } from 'next/navigation';
3-
import groq from 'groq';
44
import { getTranslations } from 'next-intl/server';
5+
56
import { client } from '@/client';
7+
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';
68
import { Link } from '@/i18n/routing';
79
import { formatBlogDate } from '@/lib/blog/date';
8-
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';
910

1011
export const revalidate = 0;
1112

@@ -189,7 +190,7 @@ function renderPortableTextBlock(block: any, index: number): React.ReactNode {
189190
return (
190191
<h6
191192
key={block._key || `block-${index}`}
192-
className="mt-6 mb-2 text-sm font-semibold uppercase tracking-wide text-gray-700 dark:text-gray-300"
193+
className="mt-6 mb-2 text-sm font-semibold tracking-wide text-gray-700 uppercase dark:text-gray-300"
193194
>
194195
{children}
195196
</h6>
@@ -209,7 +210,7 @@ function renderPortableTextBlock(block: any, index: number): React.ReactNode {
209210
return (
210211
<p
211212
key={block._key || `block-${index}`}
212-
className="mb-4 whitespace-pre-line text-base leading-relaxed text-gray-700 dark:text-gray-300"
213+
className="mb-4 text-base leading-relaxed whitespace-pre-line text-gray-700 dark:text-gray-300"
213214
>
214215
{children}
215216
</p>
@@ -283,7 +284,7 @@ function renderPortableText(
283284
key={block._key || `image-${i}`}
284285
src={block.url}
285286
alt={postTitle || 'Post image'}
286-
className="rounded-xl border border-gray-200 my-6"
287+
className="my-6 rounded-xl border border-gray-200"
287288
/>
288289
);
289290
i += 1;
@@ -482,7 +483,7 @@ export default async function PostDetails({
482483
: null;
483484

484485
return (
485-
<DynamicGridBackground className="bg-gray-50 transition-colors duration-300 dark:bg-transparent py-10">
486+
<DynamicGridBackground className="bg-gray-50 py-10 transition-colors duration-300 dark:bg-transparent">
486487
<main className="relative z-10 mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
487488
{breadcrumbsJsonLd && (
488489
<script
@@ -512,7 +513,7 @@ export default async function PostDetails({
512513
{!isLast && item.href ? (
513514
<Link
514515
href={item.href}
515-
className="transition hover:text-[var(--accent-primary)] hover:underline underline-offset-4"
516+
className="underline-offset-4 transition hover:text-[var(--accent-primary)] hover:underline"
516517
>
517518
{item.name}
518519
</Link>
@@ -533,7 +534,7 @@ export default async function PostDetails({
533534

534535
<div className="mx-auto w-full max-w-3xl">
535536
{categoryLabel && (
536-
<div className="text-sm font-medium text-gray-500 dark:text-gray-400 text-center">
537+
<div className="text-center text-sm font-medium text-gray-500 dark:text-gray-400">
537538
<Link
538539
href={categoryHref || '/blog'}
539540
className="inline-flex items-center gap-1 text-[var(--accent-primary)] transition"
@@ -542,7 +543,7 @@ export default async function PostDetails({
542543
</Link>
543544
</div>
544545
)}
545-
<h1 className="mt-3 text-4xl font-bold text-gray-900 dark:text-gray-100 text-center">
546+
<h1 className="mt-3 text-center text-4xl font-bold text-gray-900 dark:text-gray-100">
546547
{post.title}
547548
</h1>
548549

@@ -569,7 +570,7 @@ export default async function PostDetails({
569570
{(post.tags?.length || 0) > 0 && null}
570571

571572
{post.mainImage && (
572-
<div className="relative w-full h-[520px] rounded-2xl overflow-hidden my-8">
573+
<div className="relative my-8 h-[520px] w-full overflow-hidden rounded-2xl">
573574
<Image
574575
src={post.mainImage}
575576
alt={post.title || 'Post image'}
@@ -596,7 +597,7 @@ export default async function PostDetails({
596597
<h2 className="text-2xl font-semibold text-gray-900 dark:text-gray-100">
597598
{t('recommendedPosts')}
598599
</h2>
599-
<div className="mt-6 grid gap-6 auto-rows-fr sm:grid-cols-2 lg:grid-cols-3">
600+
<div className="mt-6 grid auto-rows-fr gap-6 sm:grid-cols-2 lg:grid-cols-3">
600601
{recommendedPosts.map(item => {
601602
const itemCategory = item.categories?.[0];
602603
const itemCategoryDisplay = itemCategory
@@ -619,18 +620,18 @@ export default async function PostDetails({
619620
/>
620621
</div>
621622
)}
622-
<h3 className="mt-4 text-lg font-semibold text-gray-900 transition group-hover:underline underline-offset-4 dark:text-gray-100">
623+
<h3 className="mt-4 text-lg font-semibold text-gray-900 underline-offset-4 transition group-hover:underline dark:text-gray-100">
623624
{item.title}
624625
</h3>
625626
{item.body && (
626-
<p className="mt-2 text-sm leading-relaxed text-gray-600 dark:text-gray-400 line-clamp-2">
627+
<p className="mt-2 line-clamp-2 text-sm leading-relaxed text-gray-600 dark:text-gray-400">
627628
{plainTextFromPortableText(item.body)}
628629
</p>
629630
)}
630631
{(item.author?.name ||
631632
itemCategoryDisplay ||
632633
item.publishedAt) && (
633-
<div className="mt-auto pt-3 flex flex-wrap items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
634+
<div className="mt-auto flex flex-wrap items-center gap-2 pt-3 text-sm text-gray-500 dark:text-gray-400">
634635
{item.author?.image && (
635636
<span className="relative h-5 w-5 overflow-hidden rounded-full">
636637
<Image

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import PostDetails from './PostDetails';
2-
import { client } from '@/client';
31
import groq from 'groq';
42

3+
import { client } from '@/client';
4+
5+
import PostDetails from './PostDetails';
6+
57
export async function generateStaticParams() {
68
const slugs = await client.fetch<string[]>(
79
groq`*[_type == "post" && defined(slug.current)][].slug.current`

0 commit comments

Comments
 (0)