Rocky_Mountain_Vending/app/manuals/page.tsx

154 lines
6.1 KiB
TypeScript

export const dynamic = "force-dynamic"
import { createHash } from "node:crypto"
import { existsSync } from "node:fs"
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"
import { sanitizeManualThumbnailsForRuntime } from "@/lib/manuals-render-safety"
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,
})
)
}
manuals = sanitizeManualThumbnailsForRuntime(manuals, {
isLocalDevelopment,
thumbnailsRoot: getManualsThumbnailsRoot(),
fileExists: existsSync,
})
// 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>
</>
)
}