164 lines
6.4 KiB
TypeScript
164 lines
6.4 KiB
TypeScript
export const dynamic = "force-dynamic"
|
|
|
|
import { createHash } from "node:crypto"
|
|
import { existsSync } from "fs"
|
|
import { join } from "path"
|
|
import { Metadata } from "next"
|
|
import { headers } from "next/headers"
|
|
import { businessConfig } from "@/lib/seo-config"
|
|
import { ManualsPageExperience } from "@/components/manuals-page-experience"
|
|
import { listConvexManuals } from "@/lib/convex-service"
|
|
import { scanManuals } from "@/lib/manuals"
|
|
import { selectManualsForSite } from "@/lib/manuals-site-selection"
|
|
import { generateSEOMetadata, generateStructuredData } from "@/lib/seo"
|
|
import { getManualsThumbnailsRoot } from "@/lib/manuals-paths"
|
|
import { resolveManualsTenantDomain } from "@/lib/manuals-tenant"
|
|
|
|
export const metadata: Metadata = generateSEOMetadata({
|
|
title: "Vending Machine Manuals | Rocky Mountain Vending",
|
|
description:
|
|
"Browse vending machine manuals, service guides, and support documentation for snack, beverage, combo, coffee, and food machines.",
|
|
path: "/manuals",
|
|
keywords: [
|
|
"vending machine manuals",
|
|
"vending machine PDF",
|
|
"vending machine service manual",
|
|
"vending machine repair manual",
|
|
"vending machine troubleshooting guide",
|
|
"Royal Vendors manual",
|
|
"Dixie-Narco manual",
|
|
"Vendo manual",
|
|
"Crane vending manual",
|
|
"Seaga vending manual",
|
|
],
|
|
})
|
|
|
|
export default async function ManualsPage() {
|
|
const requestHeaders = await headers()
|
|
const requestHost =
|
|
requestHeaders.get("x-forwarded-host") || requestHeaders.get("host")
|
|
const manualsDomain = resolveManualsTenantDomain({
|
|
requestHost,
|
|
envTenantDomain: process.env.MANUALS_TENANT_DOMAIN,
|
|
envSiteDomain: process.env.NEXT_PUBLIC_SITE_DOMAIN,
|
|
})
|
|
|
|
const convexManuals = manualsDomain
|
|
? await listConvexManuals(manualsDomain)
|
|
: []
|
|
|
|
const isLocalDevelopment = process.env.NODE_ENV === "development"
|
|
const shouldUseFilesystemFallback = isLocalDevelopment
|
|
|
|
const allManuals =
|
|
convexManuals.length > 0 || !shouldUseFilesystemFallback
|
|
? convexManuals
|
|
: await scanManuals()
|
|
let manuals =
|
|
convexManuals.length > 0
|
|
? convexManuals
|
|
: shouldUseFilesystemFallback
|
|
? selectManualsForSite(allManuals, manualsDomain).manuals
|
|
: []
|
|
|
|
const shouldShowDegradedState =
|
|
!shouldUseFilesystemFallback && manuals.length === 0
|
|
|
|
if (shouldShowDegradedState) {
|
|
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL || ""
|
|
const convexUrlHash = convexUrl
|
|
? createHash("sha256").update(convexUrl).digest("hex").slice(0, 12)
|
|
: "missing"
|
|
|
|
console.error(
|
|
JSON.stringify({
|
|
event: "manuals.degraded_empty_tenant",
|
|
severity: "error",
|
|
domain: manualsDomain || "missing",
|
|
host: requestHost || "missing",
|
|
manualCount: manuals.length,
|
|
convexManualCount: convexManuals.length,
|
|
convexUrlHash,
|
|
})
|
|
)
|
|
}
|
|
|
|
// Hide broken local thumbnails so the public manuals page doesn't spam 404s.
|
|
const thumbnailsRoot = getManualsThumbnailsRoot()
|
|
manuals = manuals.map((manual) => {
|
|
if (!manual.thumbnailUrl || /^https?:\/\//i.test(manual.thumbnailUrl)) {
|
|
return manual
|
|
}
|
|
|
|
const relativeThumbnailPath = manual.thumbnailUrl.includes("/thumbnails/")
|
|
? manual.thumbnailUrl.replace(/^.*\/thumbnails\//, "")
|
|
: manual.thumbnailUrl
|
|
|
|
return existsSync(join(thumbnailsRoot, relativeThumbnailPath))
|
|
? manual
|
|
: { ...manual, thumbnailUrl: undefined }
|
|
})
|
|
|
|
// Generate structured data for SEO
|
|
const structuredData = generateStructuredData({
|
|
title: "Vending Machine Manuals",
|
|
description:
|
|
"Download free PDF manuals, service guides, and parts documentation for hundreds of vending machine models from Royal Vendors, Dixie-Narco, Vendo, Crane, BevMax, Merchant Series, AP, GPL, Seaga, USI, and more. Find service manuals, parts catalogs, installation instructions, troubleshooting guides, and maintenance documentation for snack, beverage, combo, coffee, and food vending machines. Many manuals include available replacement parts with purchase links.",
|
|
url: `${businessConfig.website}/manuals`,
|
|
type: "WebPage",
|
|
})
|
|
|
|
// Add CollectionPage schema for better SEO
|
|
const collectionSchema = {
|
|
"@context": "https://schema.org",
|
|
"@type": "CollectionPage",
|
|
name: "Vending Machine Manuals",
|
|
description:
|
|
"A comprehensive collection of vending machine manuals, service guides, and parts documentation from leading manufacturers including Royal Vendors, Dixie-Narco, Vendo, Crane Merchandising, BevMax, Merchant Series, AP, GPL, Seaga, USI, and more. Includes service manuals, parts catalogs, installation instructions, troubleshooting guides, wiring diagrams, and maintenance documentation for snack machines, beverage machines, combo vending machines, coffee machines, food machines, and frozen food machines. Many manuals feature available replacement parts with direct purchase links.",
|
|
url: `${businessConfig.website}/manuals`,
|
|
mainEntity: {
|
|
"@type": "ItemList",
|
|
numberOfItems: manuals.length,
|
|
itemListElement: manuals.slice(0, 50).map((manual, index) => ({
|
|
"@type": "ListItem",
|
|
position: index + 1,
|
|
item: {
|
|
"@type": "DigitalDocument",
|
|
name: manual.filename.replace(/\.pdf$/i, ""),
|
|
description: `${manual.manufacturer} ${manual.category} Manual`,
|
|
encodingFormat: "application/pdf",
|
|
},
|
|
})),
|
|
},
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
|
/>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(collectionSchema) }}
|
|
/>
|
|
<div className="public-page" data-manuals-domain={manualsDomain}>
|
|
{shouldShowDegradedState ? (
|
|
<div className="mx-auto max-w-[var(--public-shell-max)] px-4 py-10 sm:px-5 lg:px-6">
|
|
<div className="rounded-2xl border border-destructive/30 bg-destructive/5 p-6">
|
|
<h1 className="text-xl font-semibold text-foreground">
|
|
Manuals Library Temporarily Unavailable
|
|
</h1>
|
|
<p className="mt-2 text-sm text-muted-foreground">
|
|
We are restoring tenant catalog data for this domain. Please
|
|
refresh shortly or contact support if this persists.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<ManualsPageExperience initialManuals={manuals} />
|
|
)}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|