Files
Zbrane-Reiner-Web/src/app/(frontend)/nabidka/[slug]/page.tsx
T
HonzaandClaude Opus 5 1c51034031
CI / test (push) Successful in 1m50s
CI / build-and-push (push) Successful in 2m27s
Opravit nezobrazování obrázků na webu
Payload skládá URL uploadů absolutně podle serverURL. next/image to
vyhodnotí jako externí zdroj a bez images.remotePatterns odmítne
s HTTP 400 "url parameter is not allowed" — samotný soubor se přitom
servíruje správně, takže se to projevilo až s nahraným obrázkem.

Do next/image proto posíláme jen cestu (helper cestaKSouboru). Soubor
leží na stejném originu jako web, takže je to korektní, a navíc na tom
nezávisí doména: remotePatterns se zapékají při buildu, ale tentýž image
běží na stagingu i v produkci pod jiným jménem.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:44:54 +02:00

105 lines
2.9 KiB
TypeScript

import Image from 'next/image'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { getPayload } from 'payload'
import React from 'react'
import config from '@/payload.config'
import type { Media, Product } from '@/payload-types'
import { cestaKSouboru, formatujCenu, popisStavu } from '@/lib/format'
import '../../styles.css'
export const dynamic = 'force-dynamic'
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props) {
const polozka = await najdiPolozku((await params).slug)
if (!polozka) return { title: 'Položka nenalezena' }
return {
title: `${polozka.nazev} — Reiner zbraně`,
description: polozka.popis ?? undefined,
}
}
export default async function DetailPolozky({ params }: Props) {
const polozka = await najdiPolozku((await params).slug)
if (!polozka) notFound()
const obrazky = (polozka.obrazky ?? [])
.map((radek) => radek.obrazek)
.filter((o): o is Media => typeof o === 'object' && o !== null)
return (
<article className="stranka detail">
<p className="zpet">
<Link href="/"> Zpět na nabídku</Link>
</p>
<h1>{polozka.nazev}</h1>
<p className="detail__cena">
{formatujCenu(polozka.cena)}
<span className={`znacka-stavu znacka-stavu--${polozka.stav}`}>
{popisStavu(polozka.stav)}
</span>
</p>
{obrazky.length > 0 && (
<div className="galerie">
{obrazky.map((obrazek) => {
const varianta = obrazek.sizes?.detail
const url = varianta?.url ?? obrazek.url
const width = varianta?.width ?? obrazek.width
const height = varianta?.height ?? obrazek.height
if (!url || !width || !height) return null
return (
<Image
key={obrazek.id}
alt={obrazek.alt ?? polozka.nazev}
src={cestaKSouboru(url)}
width={width}
height={height}
sizes="(max-width: 900px) 100vw, 800px"
/>
)
})}
</div>
)}
{polozka.parametry && polozka.parametry.length > 0 && (
<table className="parametry">
<tbody>
{polozka.parametry.map((parametr) => (
<tr key={parametr.id}>
<th scope="row">{parametr.nazev}</th>
<td>{parametr.hodnota}</td>
</tr>
))}
</tbody>
</table>
)}
{polozka.popis && <div className="popis">{polozka.popis}</div>}
</article>
)
}
async function najdiPolozku(slug: string): Promise<Product | null> {
const payload = await getPayload({ config: await config })
const { docs } = await payload.find({
collection: 'products',
where: {
slug: { equals: slug },
skryto: { not_equals: true },
},
depth: 1,
limit: 1,
})
return docs[0] ?? null
}