fix: polish manuals staging rollout

This commit is contained in:
DMleadgen 2026-03-27 14:04:49 -06:00
parent d6403b6296
commit e3b97f361b
Signed by: matt
GPG key ID: C2720CF8CD701894
4 changed files with 49 additions and 8 deletions

View file

@ -7,6 +7,33 @@ import { getManualAssetFromStorage } from "@/lib/manuals-object-storage"
export const dynamic = "force-dynamic" export const dynamic = "force-dynamic"
function buildPlaceholderSvg(label: string) {
const safeLabel = label
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;")
return `
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="480" viewBox="0 0 800 480" fill="none">
<rect width="800" height="480" rx="36" fill="#F5F1E6"/>
<rect x="28" y="28" width="744" height="424" rx="28" fill="#FCFBF7" stroke="#DCC9A3" stroke-width="2"/>
<circle cx="126" cy="124" r="54" fill="#E5D5B3"/>
<path d="M102 124h48M126 100v48" stroke="#8A6730" stroke-width="10" stroke-linecap="round"/>
<text x="400" y="184" text-anchor="middle" fill="#5C4320" font-size="40" font-family="Arial, sans-serif" font-weight="700">
Manual Preview
</text>
<text x="400" y="236" text-anchor="middle" fill="#8A6730" font-size="24" font-family="Arial, sans-serif">
Thumbnail unavailable
</text>
<text x="400" y="298" text-anchor="middle" fill="#7A6A4A" font-size="22" font-family="Arial, sans-serif">
${safeLabel}
</text>
</svg>
`.trim()
}
function decodeSegments(pathArray: string[]) { function decodeSegments(pathArray: string[]) {
return pathArray.map((segment) => { return pathArray.map((segment) => {
try { try {
@ -72,7 +99,14 @@ export async function GET(
const normalizedThumbnailsDir = thumbnailsDir.replace(/\\/g, "/") const normalizedThumbnailsDir = thumbnailsDir.replace(/\\/g, "/")
if (!existsSync(normalizedFullPath) && !existsSync(fullPath)) { if (!existsSync(normalizedFullPath) && !existsSync(fullPath)) {
return new NextResponse("Thumbnail not found", { status: 404 }) const label = decodedPath.at(-1)?.replace(/\.(jpg|jpeg|png|webp)$/i, "") || "Rocky Mountain Vending"
return new NextResponse(buildPlaceholderSvg(label), {
headers: {
"Content-Type": "image/svg+xml; charset=utf-8",
"Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",
"X-Content-Type-Options": "nosniff",
},
})
} }
const fileToRead = existsSync(normalizedFullPath) ? normalizedFullPath : fullPath const fileToRead = existsSync(normalizedFullPath) ? normalizedFullPath : fullPath

View file

@ -150,27 +150,27 @@ export function Footer() {
<h3 className="font-semibold mb-5 text-base">Service Areas</h3> <h3 className="font-semibold mb-5 text-base">Service Areas</h3>
<ul className="space-y-3 text-sm text-muted-foreground"> <ul className="space-y-3 text-sm text-muted-foreground">
<li> <li>
<Link href="/vending-machines/salt-lake-city-utah" className="transition-colors inline-block py-0.5"> <Link href="/vending-machines-salt-lake-city-utah" className="transition-colors inline-block py-0.5">
Salt Lake City, UT Salt Lake City, UT
</Link> </Link>
</li> </li>
<li> <li>
<Link href="/vending-machines/ogden-utah" className="transition-colors inline-block py-0.5"> <Link href="/vending-machines-ogden-utah" className="transition-colors inline-block py-0.5">
Ogden, UT Ogden, UT
</Link> </Link>
</li> </li>
<li> <li>
<Link href="/vending-machines/provo-utah" className="transition-colors inline-block py-0.5"> <Link href="/vending-machines-provo-utah" className="transition-colors inline-block py-0.5">
Provo, UT Provo, UT
</Link> </Link>
</li> </li>
<li> <li>
<Link href="/vending-machines/sandy-utah" className="transition-colors inline-block py-0.5"> <Link href="/vending-machines-sandy-utah" className="transition-colors inline-block py-0.5">
Sandy, UT Sandy, UT
</Link> </Link>
</li> </li>
<li> <li>
<Link href="/vending-machines/west-valley-city-utah" className="transition-colors inline-block py-0.5"> <Link href="/vending-machines-west-valley-city-utah" className="transition-colors inline-block py-0.5">
West Valley City, UT West Valley City, UT
</Link> </Link>
</li> </li>

View file

@ -7,6 +7,7 @@ import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogDescription,
DialogTitle, DialogTitle,
DialogClose, DialogClose,
} from '@/components/ui/dialog' } from '@/components/ui/dialog'
@ -168,6 +169,10 @@ export function ManualViewer({ manualUrl, filename, isOpen, onClose }: ManualVie
}} }}
> >
<DialogHeader className="px-4 sm:px-6 pt-4 sm:pt-6 pb-3 sm:pb-4 border-b flex-shrink-0 bg-background"> <DialogHeader className="px-4 sm:px-6 pt-4 sm:pt-6 pb-3 sm:pb-4 border-b flex-shrink-0 bg-background">
<DialogDescription className="sr-only">
PDF viewer for {filename.replace(/\.pdf$/i, '')}. Use the actions to open the manual in a new tab,
download it, or browse available parts.
</DialogDescription>
<div className="flex items-center justify-between gap-4"> <div className="flex items-center justify-between gap-4">
<DialogTitle className="text-base sm:text-lg font-semibold line-clamp-1 flex-1 min-w-0"> <DialogTitle className="text-base sm:text-lg font-semibold line-clamp-1 flex-1 min-w-0">
{filename.replace(/\.pdf$/i, '')} {filename.replace(/\.pdf$/i, '')}
@ -280,4 +285,3 @@ export function ManualViewer({ manualUrl, filename, isOpen, onClose }: ManualVie
</Dialog> </Dialog>
) )
} }

View file

@ -109,6 +109,10 @@ async function searchEBayForParts(partNumber: string, description?: string, manu
* Enhance parts data with real-time eBay listings * Enhance parts data with real-time eBay listings
*/ */
async function enhancePartsData(parts: PartForPage[]): Promise<PartForPage[]> { async function enhancePartsData(parts: PartForPage[]): Promise<PartForPage[]> {
if (!ebayClient.isConfigured()) {
return parts
}
const enhancedParts = await Promise.all(parts.map(async (part) => { const enhancedParts = await Promise.all(parts.map(async (part) => {
// Only search for parts without existing eBay listings // Only search for parts without existing eBay listings
if (part.ebayListings.length === 0) { if (part.ebayListings.length === 0) {
@ -261,4 +265,3 @@ export function clearPartsCache(): void {
manualPartsCache = null manualPartsCache = null
manualPagesPartsCache = null manualPagesPartsCache = null
} }