Fetches and injects JSON-LD plus a copy-friendly rich-results debug panel.
No JSON-LD for this point.
bunx shadcn@latest add https://mapbase.dev/r/seo-json-ld-preview.jsonThe shadcn CLI copies the file(s) below into your tree, installs npm dependencies, and recursively pulls registry dependencies (including from cross-registry URLs).
"use client"
import * as React from "react"
import { Mapbase, MapbaseError, type CountryCode } from "mapbase"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { SeoJsonLd } from "@/components/mapbase/seo-json-ld"
export type SeoJsonLdPreviewProps = {
apiKey: string
baseUrl?: string
initialLng?: number
initialLat?: number
country?: CountryCode
siteBaseUrl?: string
className?: string
}
const DEFAULT_LNG = -9.1393
const DEFAULT_LAT = 38.7223
/**
* Card that fetches JSON-LD for coordinates, injects script tags, and
* shows a copy-friendly preview for rich-result debugging.
*/
export function SeoJsonLdPreview({
apiKey,
baseUrl,
initialLng = DEFAULT_LNG,
initialLat = DEFAULT_LAT,
country,
siteBaseUrl = "https://example.com",
className,
}: SeoJsonLdPreviewProps) {
const [lng, setLng] = React.useState(initialLng)
const [lat, setLat] = React.useState(initialLat)
const [lngInput, setLngInput] = React.useState(String(initialLng))
const [latInput, setLatInput] = React.useState(String(initialLat))
const [raw, setRaw] = React.useState<string | null>(null)
const [error, setError] = React.useState<MapbaseError | null>(null)
const [loading, setLoading] = React.useState(false)
const [copied, setCopied] = React.useState(false)
const client = React.useMemo(
() => new Mapbase(apiKey, baseUrl ? { baseUrl } : undefined),
[apiKey, baseUrl],
)
const fetchJsonLd = React.useCallback(async () => {
setLoading(true)
setError(null)
const response = await client.seo.jsonLd({
lng,
lat,
...(country ? { country } : {}),
...(siteBaseUrl ? { base_url: siteBaseUrl } : {}),
})
setLoading(false)
if (response.error) {
setError(response.error)
setRaw(null)
return
}
setRaw(
JSON.stringify(
{
breadcrumb_list: response.data.breadcrumb_list,
place: response.data.place,
},
null,
2,
),
)
}, [client, lng, lat, country, siteBaseUrl])
React.useEffect(() => {
void fetchJsonLd()
}, [fetchJsonLd])
const applyCoords = () => {
const nextLng = Number.parseFloat(lngInput)
const nextLat = Number.parseFloat(latInput)
if (!Number.isFinite(nextLng) || !Number.isFinite(nextLat)) return
setLng(nextLng)
setLat(nextLat)
}
const copy = async () => {
if (!raw) return
await navigator.clipboard.writeText(raw)
setCopied(true)
window.setTimeout(() => setCopied(false), 2000)
}
return (
<Card className={className}>
<CardHeader>
<CardTitle>JSON-LD preview</CardTitle>
<CardDescription>
Schema.org BreadcrumbList and Place for local SEO rich results.
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="flex flex-wrap items-end gap-3">
<div className="flex min-w-[8rem] flex-col gap-1.5">
<Label htmlFor="seo-ld-lng" className="text-xs">
Longitude
</Label>
<Input
id="seo-ld-lng"
inputMode="decimal"
value={lngInput}
onChange={(e) => setLngInput(e.target.value)}
className="h-8 font-mono text-sm"
/>
</div>
<div className="flex min-w-[8rem] flex-col gap-1.5">
<Label htmlFor="seo-ld-lat" className="text-xs">
Latitude
</Label>
<Input
id="seo-ld-lat"
inputMode="decimal"
value={latInput}
onChange={(e) => setLatInput(e.target.value)}
className="h-8 font-mono text-sm"
/>
</div>
<Button type="button" size="sm" className="h-8" onClick={applyCoords}>
Apply
</Button>
</div>
<SeoJsonLd
apiKey={apiKey}
baseUrl={baseUrl}
lng={lng}
lat={lat}
country={country}
siteBaseUrl={siteBaseUrl}
/>
{loading ? (
<p className="text-xs text-muted-foreground">Refreshing preview…</p>
) : null}
{error ? (
<p className="text-xs text-destructive">{error.message}</p>
) : null}
{raw ? (
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between gap-2">
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
Copy payload
</p>
<Button type="button" variant="outline" size="sm" onClick={copy}>
{copied ? "Copied" : "Copy JSON"}
</Button>
</div>
<pre className="max-h-64 overflow-auto rounded-md border bg-muted/30 p-3 font-mono text-xs text-muted-foreground">
{raw}
</pre>
</div>
) : null}
</CardContent>
</Card>
)
}