Skip to content

Commit 03b3871

Browse files
authored
Header UX polish, quiz highlight fix, Blog button styling, shop i18n product descriptions (#322)
* Header UX: reorder languages, swap controls, fix quiz highlight, style Blog button * shop i18n product descriptions
1 parent dcd8c0f commit 03b3871

14 files changed

Lines changed: 134 additions & 23 deletions

File tree

frontend/app/[locale]/shop/products/[slug]/page.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ArrowLeft } from 'lucide-react';
22
import { Metadata } from 'next';
33
import Image from 'next/image';
44
import { notFound } from 'next/navigation';
5-
import { getTranslations } from 'next-intl/server';
5+
import { getMessages, getTranslations } from 'next-intl/server';
66

77
import { AddToCartButton } from '@/components/shop/AddToCartButton';
88
import { getPublicProductBySlug } from '@/db/queries/shop/products';
@@ -54,6 +54,9 @@ export default async function ProductPage({
5454
'text-lg',
5555
'items-center gap-2'
5656
);
57+
const messages = await getMessages();
58+
const productDescriptions =
59+
(messages as any).shop?.productDescriptions ?? {};
5760
const badge = product?.badge as string | undefined;
5861
const badgeLabel =
5962
badge && badge !== 'NONE'
@@ -131,9 +134,18 @@ export default async function ProductPage({
131134
</section>
132135
)}
133136

134-
{product.description && (
135-
<p className="text-muted-foreground mt-6">{product.description}</p>
136-
)}
137+
{(() => {
138+
const desc =
139+
(productDescriptions[slug] as string) || product.description;
140+
if (!desc) return null;
141+
return (
142+
<div className="text-muted-foreground mt-6 space-y-2">
143+
{desc.split('\n').map((line: string, i: number) => (
144+
<p key={i}>{line}</p>
145+
))}
146+
</div>
147+
);
148+
})()}
137149

138150
{!isUnavailable && (
139151
<section aria-label="Purchase">

frontend/components/header/AppMobileMenu.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,9 @@ export function AppMobileMenu({
243243
{variant === 'platform' && (
244244
<>
245245
{links
246-
.filter(link => link.href !== '/shop')
246+
.filter(
247+
link => link.href !== '/shop' && link.href !== '/blog'
248+
)
247249
.map(link => (
248250
<Link
249251
key={link.href}
@@ -255,6 +257,15 @@ export function AppMobileMenu({
255257
</Link>
256258
))}
257259

260+
<HeaderButton
261+
href="/blog"
262+
icon={BookOpen}
263+
showArrow
264+
onLinkClick={handleHeaderButtonLinkClick('/blog')}
265+
>
266+
{t('blog')}
267+
</HeaderButton>
268+
258269
<HeaderButton
259270
href="/shop"
260271
icon={ShoppingBag}

frontend/components/header/DesktopActions.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export function DesktopActions({
2727

2828
return (
2929
<div className="hidden items-center gap-2 lg:flex">
30+
{isBlog && <BlogHeaderSearch />}
31+
32+
<LanguageSwitcher />
33+
3034
{userExists && (
3135
<HeaderButton
3236
variant="icon"
@@ -45,10 +49,6 @@ export function DesktopActions({
4549
/>
4650
)}
4751

48-
{isBlog && <BlogHeaderSearch />}
49-
50-
<LanguageSwitcher />
51-
5252
{isShop && <CartButton />}
5353

5454
{!userExists ? (

frontend/components/header/DesktopNav.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { ShoppingBag } from 'lucide-react';
3+
import { BookOpen, ShoppingBag } from 'lucide-react';
44
import { useTranslations } from 'next-intl';
55

66
import { BlogCategoryLinks } from '@/components/blog/BlogCategoryLinks';
@@ -41,13 +41,19 @@ export function DesktopNav({ variant, blogCategories = [] }: DesktopNavProps) {
4141
return (
4242
<div className="flex items-center gap-2">
4343
<div className="flex items-center gap-1">
44-
{SITE_LINKS.filter(link => link.href !== '/shop').map(link => (
44+
{SITE_LINKS.filter(
45+
link => link.href !== '/shop' && link.href !== '/blog'
46+
).map(link => (
4547
<NavLink key={link.href} href={link.href}>
4648
{t(link.labelKey)}
4749
</NavLink>
4850
))}
4951
</div>
5052

53+
<HeaderButton href="/blog" icon={BookOpen} showArrow>
54+
{t('blog')}
55+
</HeaderButton>
56+
5157
<HeaderButton
5258
href="/shop"
5359
icon={ShoppingBag}

frontend/components/header/MobileActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ export function MobileActions({
2828

2929
return (
3030
<div className="flex items-center gap-1 lg:hidden">
31-
{isBlog && <BlogHeaderSearch />}
3231
<LanguageSwitcher />
32+
{isBlog && <BlogHeaderSearch />}
3333
{isShop && <CartButton />}
3434
<AppMobileMenu
3535
variant={variant}

frontend/components/header/MobileMenuContext.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
3+
import { usePathname, useSearchParams } from 'next/navigation';
44
import {
55
createContext,
66
ReactNode,
@@ -11,6 +11,8 @@ import {
1111
useState,
1212
} from 'react';
1313

14+
import { useRouter } from '@/i18n/routing';
15+
1416
type MobileMenuContextType = {
1517
isOpen: boolean;
1618
isAnimating: boolean;
@@ -71,9 +73,7 @@ export function MobileMenuProvider({ children }: { children: ReactNode }) {
7173
const [targetPath, targetSearch = ''] = href.split('?');
7274
const currentSearch = searchParams.toString();
7375

74-
// pathname from next/navigation includes the locale prefix (e.g. /uk/shop)
75-
// Strip it so we can compare with href which has no locale (e.g. /shop)
76-
const strippedPathname = pathname.replace(/^\/(uk|en|pl)/, '') || '/';
76+
const strippedPathname = pathname.replace(/^\/(en|uk|pl)/, '') || '/';
7777

7878
if (strippedPathname === targetPath && targetSearch === currentSearch) {
7979
return;

frontend/components/header/NavLink.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ export function NavLink({ href, children, className = '' }: NavLinkProps) {
1919
if (href === '/' && cleanPathname === '/') return true;
2020

2121
if (href !== '/') {
22-
return cleanPathname === href || cleanPathname.startsWith(`${href}/`);
22+
if (cleanPathname === href || cleanPathname.startsWith(`${href}/`))
23+
return true;
24+
if (href === '/quizzes' && cleanPathname.startsWith('/quiz/'))
25+
return true;
2326
}
2427

2528
return false;

frontend/components/leaderboard/LeaderboardClient.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default function LeaderboardClient({
5757
</header>
5858

5959
<div className="flex w-full flex-col items-center">
60-
<div className="mb-24 w-full">
60+
<div className="mb-10 w-full">
6161
{hasResults ? (
6262
<LeaderboardPodium topThree={topThree} />
6363
) : (

frontend/components/leaderboard/LeaderboardTable.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export function LeaderboardTable({
3939
<div className="w-full">
4040
<table className="w-full table-fixed border-separate border-spacing-0 text-left">
4141
<caption className="sr-only">{t('tableCaption')}</caption>
42+
<colgroup>
43+
<col className="w-[15%] sm:w-[12%]" />
44+
<col />
45+
<col className="w-[25%] sm:w-[20%]" />
46+
</colgroup>
4247

4348
<thead className="bg-slate-50/80 dark:bg-white/5">
4449
<tr>
@@ -83,6 +88,11 @@ export function LeaderboardTable({
8388
<div className="overflow-hidden rounded-2xl border-2 border-(--accent-primary) bg-white shadow-[0_0_20px_var(--accent-primary)] backdrop-blur-md dark:bg-white/5">
8489
<div className="w-full">
8590
<table className="w-full table-fixed border-separate border-spacing-0 text-left">
91+
<colgroup>
92+
<col className="w-[15%] sm:w-[12%]" />
93+
<col />
94+
<col className="w-[25%] sm:w-[20%]" />
95+
</colgroup>
8696
<tbody>
8797
<TableRow user={matchedUser} isCurrentUser={true} t={t} />
8898
</tbody>

frontend/components/shared/LanguageSwitcher.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { type Locale, locales } from '@/i18n/config';
88
import { Link } from '@/i18n/routing';
99

1010
const localeLabels: Record<Locale, string> = {
11-
uk: 'UA',
1211
en: 'EN',
12+
uk: 'UA',
1313
pl: 'PL',
1414
};
1515

@@ -69,7 +69,7 @@ export default function LanguageSwitcher() {
6969
</button>
7070

7171
{isOpen && (
72-
<div className="absolute right-0 z-[70] mt-2 w-20 rounded-md border border-gray-200 bg-white py-2 shadow-lg dark:border-neutral-800 dark:bg-neutral-900">
72+
<div className="absolute right-0 z-70 mt-2 w-20 rounded-md border border-gray-200 bg-white py-2 shadow-lg dark:border-neutral-800 dark:bg-neutral-900">
7373
{locales.map(locale => (
7474
<Link
7575
key={locale}

0 commit comments

Comments
 (0)