Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
036c097
feat(medusa): add storefront text translations
KaiUweCZE Jul 9, 2026
93a94cb
feat(herbatika): load storefront text translations
KaiUweCZE Jul 9, 2026
59f1a09
fix(herbatika): harden storefront text translations
KaiUweCZE Jul 9, 2026
68785ee
feat(herbatika): centralize add-to-cart translations
KaiUweCZE Jul 11, 2026
2cab169
feat(herbatika): localize mini cart UI
KaiUweCZE Jul 11, 2026
94bb158
feat(herbatika): localize checkout core flow
KaiUweCZE Jul 12, 2026
6f74b2b
fix(herbatika): preserve literal storefront text values
KaiUweCZE Jul 13, 2026
c868cb2
eat(herbatika): localize checkout form fields and validation
KaiUweCZE Jul 13, 2026
2a5b859
feat(herbatika): localize checkout customer details
KaiUweCZE Jul 13, 2026
1a70062
fix(herbatika): align commerce region with market country
KaiUweCZE Jul 14, 2026
1099d45
feat(medusa): separate storefront text defaults and overrides
KaiUweCZE Jul 14, 2026
199fb4d
feat(storefront-i18n): add shared next-intl integration
KaiUweCZE Jul 14, 2026
81467f7
feat(herbatika): localize search autocomplete
KaiUweCZE Jul 15, 2026
100ec5f
chore(herbatika): add Next.js agent guidance
KaiUweCZE Jul 15, 2026
f67e0a3
feat(herbatika): localize catalog listing states
KaiUweCZE Jul 15, 2026
64da17c
feat(herbatika): localize catalog filters and pagination
KaiUweCZE Jul 15, 2026
ca1f70d
feat(herbatika): localize catalog navigation
KaiUweCZE Jul 15, 2026
fa4d3b0
feat(medusa): validate storefront text ICU messages
KaiUweCZE Jul 16, 2026
728ac0c
feat(herbatika): localize checkout entry and shared actions
KaiUweCZE Jul 16, 2026
7ba992b
feat(herbatika): localize checkout sidebar controls
KaiUweCZE Jul 16, 2026
31ef7ea
feat(herbatika): localize carrier pickup selection
KaiUweCZE Jul 16, 2026
5b7cc8f
feat(herbatika): localize payment return states
KaiUweCZE Jul 16, 2026
7d3a579
feat(herbatika): localize completed order and QR payment
KaiUweCZE Jul 16, 2026
d8eef18
feat(herbatika): localize checkout review
KaiUweCZE Jul 17, 2026
1309fde
feat(herbatika): localize checkout payment methods
KaiUweCZE Jul 17, 2026
85d11d5
feat(herbatika): localize checkout free shipping progress
KaiUweCZE Jul 17, 2026
377151c
feat(herbatika): localize customer authentication
KaiUweCZE Jul 17, 2026
74aae79
feat(herbatika): localize brand metadata and category controls
KaiUweCZE Jul 17, 2026
9144050
feat(herbatika): localize account and order UI
KaiUweCZE Jul 17, 2026
7f8e9b1
feat(herbatika): localize product cards and product lists
KaiUweCZE Jul 18, 2026
2224d17
feat(herbatika): localize product reviews
KaiUweCZE Jul 18, 2026
0239311
feat(herbatika): localize product detail UI
KaiUweCZE Jul 18, 2026
9b99535
feat(herbatika): localize checkout product suggestions
KaiUweCZE Jul 18, 2026
b2d6851
feat(herbatika): localize content UI chrome
KaiUweCZE Jul 18, 2026
91255a8
feat(herbatika): localize header navigation states
KaiUweCZE Jul 18, 2026
486878e
feat(herbatika): localize footer legal chrome
KaiUweCZE Jul 18, 2026
94e6750
fix(herbatika): stop fabricating delivery dates
KaiUweCZE Jul 18, 2026
07c7839
feat(herbatika): localize homepage section copy
KaiUweCZE Jul 19, 2026
fcd1af7
feat(medusa): add value-only storefront text search
KaiUweCZE Jul 19, 2026
9b7bbb1
feat(medusa): localize storefront texts admin
KaiUweCZE Jul 20, 2026
6b612fc
refactor(medusa): centralize SQL LIKE escaping
KaiUweCZE Jul 21, 2026
68974db
feat(medusa): add storefront text catalog management
KaiUweCZE Jul 21, 2026
a4d515f
refactor(herbatika): reuse localized validation messages
KaiUweCZE Jul 21, 2026
60691ff
fix(storefront): address localization review findings
KaiUweCZE Jul 21, 2026
c39ba06
update lockfile
KaiUweCZE Jul 21, 2026
6e6159e
fix(style): clean rebased storefront localization
redeyecz Aug 3, 2026
f5ff7a2
fix(storefront-text): migrations generated by medusa
redeyecz Aug 3, 2026
32193e6
fix(herbatika): biome
redeyecz Aug 3, 2026
195e9ae
fix(herbatika): greptile finding fix
redeyecz Aug 3, 2026
f5572ed
fix(medusa): lint
redeyecz Aug 3, 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
10 changes: 9 additions & 1 deletion apps/herbatika/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
- From the repo root, prefer `pnpm -C apps/herbatika <script>` for app scripts.
- Do not use the root frontend-demo `localhost:3000` assumption for this app.
- `pnpm build` builds production output; `pnpm start` serves the production build.
- Temporary rule: do not run Biome commands in this app until stability issue is resolved.
- Run `pnpm -C apps/herbatika lint` before handoff to match the Nx/CI Biome check.

## Testing Workflow
- For manual QA, use skill `local-web-testing`.
Expand All @@ -69,3 +69,11 @@
- `~/.local/share/medusa`
- `~/.local/share/zagjs`
- If local mirrors are missing/outdated, use official upstream docs.

<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.

**Keep this block, including in commits.** It is part of the project's agent setup, maintained by `next dev` for every agent that works here. If it appears as an uncommitted change, that is intentional — commit it as-is. Do not remove it to clean up a diff; it will be regenerated.
<!-- END:nextjs-agent-rules -->
17 changes: 15 additions & 2 deletions apps/herbatika/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { join } from "node:path"
import type { NextConfig } from "next"
import createNextIntlPlugin from "next-intl/plugin"

const withNextIntl = createNextIntlPlugin()

type ImageRemotePattern = {
protocol: "http" | "https"
Expand Down Expand Up @@ -52,9 +55,19 @@ const shouldDisableImageOptimization = imageRemotePatterns.some(
)

const nextConfig: NextConfig = {
allowedDevOrigins: [
"herbatica.sk",
"herbatica.cz",
"herbatica.hu",
"herbatica.ro",
],
reactStrictMode: true,
output: "standalone",
transpilePackages: ["@techsio/ui-kit", "@techsio/storefront-data"],
transpilePackages: [
"@techsio/ui-kit",
"@techsio/storefront-data",
"@techsio/storefront-i18n",
],
reactCompiler: true,
cacheComponents: true,
outputFileTracingRoot: join(__dirname, "../../"),
Expand Down Expand Up @@ -96,4 +109,4 @@ const nextConfig: NextConfig = {
},
}

export default nextConfig
export default withNextIntl(nextConfig)
2 changes: 2 additions & 0 deletions apps/herbatika/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
"@tanstack/react-form": "^1.27.4",
"@tanstack/react-query": "^5.90.2",
"@techsio/storefront-data": "workspace:*",
"@techsio/storefront-i18n": "workspace:*",
"@techsio/ui-kit": "workspace:*",
"next": "16.3.0-preview.5",
"next-intl": "^4.13.2",
"nuqs": "^2.8.8",
"qrcode": "1.5.4",
"react": "19.2.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
"use client"

import { useTranslations } from "next-intl"
import { useState } from "react"
import { ForgotPasswordForm } from "@/components/auth/forgot-password-form"
import { requestPasswordResetProxy } from "@/lib/storefront/auth/proxy"

const LOGIN_HREF = "/auth/login"

export const ForgotPasswordPanel = () => {
const tAuth = useTranslations("auth")
const [isBusy, setIsBusy] = useState(false)

const handleSubmit = async (values: { email: string }) => {
setIsBusy(true)
try {
await requestPasswordResetProxy(values.email)
return null
} catch (error) {
return error instanceof Error
? error.message
: "Nepodarilo sa odoslať odkaz na obnovu hesla."
} catch {
return tAuth("forgot.failed")
} finally {
setIsBusy(false)
}
Expand All @@ -26,10 +26,9 @@ export const ForgotPasswordPanel = () => {
return (
<section className="mx-auto max-w-max-w space-y-400 p-400">
<header className="space-y-200">
<h1 className="font-semibold text-lg">Zabudnuté heslo</h1>
<h1 className="font-semibold text-lg">{tAuth("forgot.title")}</h1>
<p className="text-fg-secondary text-sm">
Zadajte e-mailovú adresu, na ktorú máte vytvorený účet. Pošleme vám
odkaz na obnovu hesla.
{tAuth("forgot.description")}
</p>
</header>

Expand Down
76 changes: 31 additions & 45 deletions apps/herbatika/src/app/auth/reset-password/reset-password-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client"

import { useTranslations } from "next-intl"
import { useState } from "react"
import { ResetPasswordForm } from "@/components/auth/reset-password-form"
import { requestPasswordUpdateProxy } from "@/lib/storefront/auth/proxy"
Expand All @@ -15,55 +16,40 @@ type ResetPasswordPanelProps = {
flow: ResetPasswordFlow
}

const getResetPasswordCopy = ({
email,
flow,
}: {
email: string | null
flow: ResetPasswordFlow
}) => {
if (flow === "account-setup") {
return {
description: email
? `Dokončite registráciu účtu ${email} nastavením hesla.`
: "Dokončite registráciu účtu nastavením hesla.",
expiredHref: FORGOT_PASSWORD_HREF,
expiredHelp:
"Na prihlasovacej stránke si môžete vyžiadať nový odkaz na nastavenie hesla.",
expiredLinkLabel: "Vyžiadať nový odkaz na nastavenie hesla",
expiredMessage:
"Tento odkaz na dokončenie registrácie je neplatný alebo už vypršal.",
submitError: "Nepodarilo sa nastaviť heslo.",
submitLabel: "Nastaviť heslo",
successMessage: "Heslo bolo úspešne nastavené. Môžete sa prihlásiť.",
title: "Nastavenie hesla",
}
}

return {
description: email
? `Nastavte nové heslo pre účet ${email}.`
: "Zadajte nové heslo pre váš účet.",
expiredHref: FORGOT_PASSWORD_HREF,
expiredHelp: "Skúste si vyžiadať nový odkaz na obnovu hesla.",
expiredLinkLabel: "Vyžiadať nový odkaz",
expiredMessage: "Tento odkaz je neplatný alebo už vypršal.",
submitError: "Nepodarilo sa obnoviť heslo.",
submitLabel: "Obnoviť heslo",
successMessage:
"Heslo bolo úspešne zmenené. Môžete sa prihlásiť pomocou nového hesla.",
title: "Obnova hesla",
}
}

export const ResetPasswordPanel = ({
token,
email,
flow,
}: ResetPasswordPanelProps) => {
const tAuth = useTranslations("auth")
const [isBusy, setIsBusy] = useState(false)
const hasToken = Boolean(token)
const copy = getResetPasswordCopy({ email, flow })
const copy =
flow === "account-setup"
? {
description: email
? tAuth("account_setup.description_with_email", { email })
: tAuth("account_setup.description"),
expiredHelp: tAuth("account_setup.expired_help"),
expiredLinkLabel: tAuth("account_setup.expired_link"),
expiredMessage: tAuth("account_setup.expired_message"),
submitError: tAuth("account_setup.failed"),
submitLabel: tAuth("account_setup.submit"),
successMessage: tAuth("account_setup.success"),
title: tAuth("account_setup.title"),
}
: {
description: email
? tAuth("reset.description_with_email", { email })
: tAuth("reset.description"),
expiredHelp: tAuth("reset.expired_help"),
expiredLinkLabel: tAuth("reset.expired_link"),
expiredMessage: tAuth("reset.expired_message"),
submitError: tAuth("reset.failed"),
submitLabel: tAuth("reset.submit"),
successMessage: tAuth("reset.success"),
title: tAuth("reset.title"),
}

const handleSubmit = async (values: {
password: string
Expand All @@ -80,8 +66,8 @@ export const ResetPasswordPanel = ({
token,
})
return null
} catch (error) {
return error instanceof Error ? error.message : copy.submitError
} catch {
return copy.submitError
} finally {
setIsBusy(false)
}
Expand All @@ -101,7 +87,7 @@ export const ResetPasswordPanel = ({
loginHref={LOGIN_HREF}
onSubmit={handleSubmit}
text={{
expiredHref: copy.expiredHref,
expiredHref: FORGOT_PASSWORD_HREF,
expiredHelp: copy.expiredHelp,
expiredLinkLabel: copy.expiredLinkLabel,
expiredMessage: copy.expiredMessage,
Expand Down
69 changes: 55 additions & 14 deletions apps/herbatika/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import type { RegionInfo } from "@techsio/storefront-data/shared/region"
import type { Metadata } from "next"
import { Inter, Open_Sans, Roboto, Rubik } from "next/font/google"
import localFont from "next/font/local"
import { type AbstractIntlMessages, NextIntlClientProvider } from "next-intl"
import { getMessages } from "next-intl/server"
import { Suspense } from "react"
import { AppShell } from "@/components/app-shell"
import {
buildCategoryListParams,
CATEGORY_TREE_FIELDS,
CATEGORY_TREE_LIMIT,
} from "@/lib/storefront/category-query-config"
import type { HerbatikaMarketContext } from "@/lib/storefront/market-context"
Comment thread
KaiUweCZE marked this conversation as resolved.
import { getMarketServerContext } from "@/lib/storefront/market-context.server"
import { getRegionServerContext } from "@/lib/storefront/ssr/context"
import { fetchServerCategories } from "@/lib/storefront/storefront-server"
import "./globals.css"
Expand Down Expand Up @@ -61,39 +65,58 @@ const roboto = Roboto({
display: "swap",
})

export const metadata: Metadata = {
title: "Herbatica",
description: "Herbatica e-shop - prírodné produkty",
export async function generateMetadata(): Promise<Metadata> {
const marketContext = await getMarketServerContext()

return {
title: marketContext.metadata.title,
description: marketContext.metadata.description,
}
}

type LayoutShellProps = Readonly<{
children: React.ReactNode
dehydratedState: DehydratedState
initialRegion?: RegionInfo | null
marketContext: HerbatikaMarketContext
messages: AbstractIntlMessages
}>

function LayoutShell({
children,
dehydratedState,
initialRegion = null,
marketContext,
messages,
}: LayoutShellProps) {
return (
<Providers initialRegion={initialRegion}>
<HydrationBoundary state={dehydratedState}>
<Suspense fallback={<div className="min-h-dvh bg-base" />}>
<AppShell>{children}</AppShell>
</Suspense>
</HydrationBoundary>
</Providers>
<NextIntlClientProvider messages={messages}>
<Providers
initialMarketContext={marketContext}
initialRegion={initialRegion}
>
<HydrationBoundary state={dehydratedState}>
<Suspense fallback={<div className="min-h-dvh bg-base" />}>
<AppShell>{children}</AppShell>
</Suspense>
</HydrationBoundary>
</Providers>
</NextIntlClientProvider>
)
}

async function ResolvedLayoutShell({
children,
marketContext,
}: Readonly<{
children: React.ReactNode
marketContext: HerbatikaMarketContext
}>) {
const { queryClient, region } = await getRegionServerContext()
const [{ queryClient, region }, messages] = await Promise.all([
getRegionServerContext(),
getMessages(),
])

try {
await fetchServerCategories(
queryClient,
Expand All @@ -111,31 +134,49 @@ async function ResolvedLayoutShell({
<LayoutShell
dehydratedState={dehydrate(queryClient)}
initialRegion={region}
marketContext={marketContext}
messages={messages}
>
{children}
</LayoutShell>
)
}

export default function RootLayout({
async function ResolvedRootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
const marketContext = await getMarketServerContext()

return (
Comment thread
greptile-apps[bot] marked this conversation as resolved.
<html
className={`${verdana.variable} ${openSans.variable} ${inter.variable} ${rubik.variable} ${roboto.variable}`}
lang="sk"
lang={marketContext.htmlLang}
>
<body className={`text-fg-primary ${verdana.className}`}>
<Suspense
// Avoid rendering a fallback app shell here. During streaming, it can
// coexist with the resolved shell and duplicate header popover ids.
fallback={<div className="min-h-dvh bg-base" />}
>
<ResolvedLayoutShell>{children}</ResolvedLayoutShell>
<ResolvedLayoutShell marketContext={marketContext}>
{children}
</ResolvedLayoutShell>
</Suspense>
</body>
</html>
)
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<Suspense fallback={null}>
Comment thread
greptile-apps[bot] marked this conversation as resolved.
<ResolvedRootLayout>{children}</ResolvedRootLayout>
</Suspense>
)
}
Loading
Loading