Rocky_Mountain_Vending/components/parts-panel.tsx
DMleadgen 46d973904b
Initial commit: Rocky Mountain Vending website
Next.js website for Rocky Mountain Vending company featuring:
- Product catalog with Stripe integration
- Service areas and parts pages
- Admin dashboard with Clerk authentication
- SEO optimized pages with JSON-LD structured data

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 16:22:15 -07:00

200 lines
8.1 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { ExternalLink, ShoppingCart, Loader2, AlertCircle } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { getTopPartsForManual, type PartForPage } from '@/lib/parts-lookup'
interface PartsPanelProps {
manualFilename: string
className?: string
}
export function PartsPanel({ manualFilename, className = '' }: PartsPanelProps) {
const [parts, setParts] = useState<PartForPage[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [isSearching, setIsSearching] = useState(false)
useEffect(() => {
async function loadParts() {
setIsLoading(true)
setIsSearching(false)
setError(null)
try {
const topParts = await getTopPartsForManual(manualFilename, 5)
setParts(topParts)
} catch (err) {
console.error('Error loading parts:', err)
setError('Could not load parts')
} finally {
setIsLoading(false)
}
}
if (manualFilename) {
loadParts()
}
}, [manualFilename])
if (isLoading) {
return (
<div className={`flex flex-col h-full ${className}`}>
<div className="px-3 py-2 border-b border-yellow-300/20 flex-shrink-0 bg-yellow-100/50 dark:bg-yellow-900/30">
<div className="flex items-center gap-1.5">
<ShoppingCart className="h-3.5 w-3.5 text-yellow-900 dark:text-yellow-100" />
<span className="text-xs font-semibold text-yellow-900 dark:text-yellow-100">
Parts
</span>
</div>
</div>
<div className="px-3 py-3 text-sm text-yellow-900/70 dark:text-yellow-100/70 flex items-center justify-center">
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Loading parts...
</div>
</div>
)
}
if (error || parts.length === 0) {
return (
<div className={`flex flex-col h-full ${className}`}>
<div className="px-3 py-2 border-b border-yellow-300/20 flex-shrink-0 bg-yellow-100/50 dark:bg-yellow-900/30">
<div className="flex items-center gap-1.5">
<ShoppingCart className="h-3.5 w-3.5 text-yellow-900 dark:text-yellow-100" />
<span className="text-xs font-semibold text-yellow-900 dark:text-yellow-100">
Parts
</span>
</div>
</div>
<div className="px-3 py-3 text-xs text-yellow-900/70 dark:text-yellow-100/70 flex items-center justify-center">
<AlertCircle className="h-4 w-4 mr-2 text-yellow-700 dark:text-yellow-300" />
No parts available
</div>
</div>
)
}
return (
<div className={`flex flex-col h-full overflow-hidden ${className}`}>
<div className="px-3 py-2 border-b border-yellow-300/20 flex-shrink-0 bg-yellow-100/50 dark:bg-yellow-900/30">
<div className="flex items-center gap-1.5">
<ShoppingCart className="h-3.5 w-3.5 text-yellow-900 dark:text-yellow-100" />
<span className="text-xs font-semibold text-yellow-900 dark:text-yellow-100">
Parts ({parts.length})
</span>
</div>
</div>
<div className="flex-1 overflow-y-auto px-3 py-2 space-y-2">
{parts.map((part, index) => (
<div
key={`${part.partNumber}-${index}`}
className="bg-white/60 dark:bg-yellow-900/20 rounded border border-yellow-300/30 dark:border-yellow-700/30 p-2 space-y-1.5"
>
{/* Part Header */}
<div className="space-y-1">
<div className="text-xs font-semibold text-yellow-900 dark:text-yellow-100 truncate">
{part.partNumber}
</div>
{part.description && (
<div className="text-[10px] text-yellow-800/80 dark:text-yellow-200/70 line-clamp-1">
{part.description}
</div>
)}
</div>
{/* eBay Listings */}
{part.ebayListings.length > 0 && (
<div className="space-y-1.5">
{part.ebayListings.slice(0, 2).map((listing, listingIndex) => (
<a
key={listing.itemId}
href={listing.affiliateLink}
target="_blank"
rel="noopener noreferrer"
className="block group"
>
<div className="bg-white dark:bg-yellow-900/30 rounded border border-yellow-300/40 dark:border-yellow-700/40 p-1.5 hover:bg-yellow-50 dark:hover:bg-yellow-900/40 transition-colors">
{/* Image */}
{listing.imageUrl && (
<div className="mb-1.5 rounded overflow-hidden bg-yellow-100 dark:bg-yellow-900/50">
<img
src={listing.imageUrl}
alt={listing.title}
className="w-full h-20 object-cover"
onError={(e) => {
e.currentTarget.src = `https://via.placeholder.com/150x80/fbbf24/1f2937?text=${encodeURIComponent(part.partNumber)}`
}}
/>
</div>
)}
{!listing.imageUrl && (
<div className="mb-1.5 rounded overflow-hidden bg-yellow-100 dark:bg-yellow-900/50 h-20 flex items-center justify-center">
<span className="text-[10px] text-yellow-700 dark:text-yellow-300">{part.partNumber}</span>
</div>
)}
{/* Listing Details */}
<div className="space-y-1">
<div className="text-[10px] text-yellow-900 dark:text-yellow-100 line-clamp-2 min-h-[1.5rem]">
{listing.title}
</div>
<div className="flex items-center justify-between">
<div className="flex flex-col gap-0.5">
<span className="text-xs font-semibold text-yellow-900 dark:text-yellow-100">
{listing.price}
</span>
{listing.shippingCost && (
<span className="text-[9px] text-yellow-700 dark:text-yellow-300">
{listing.shippingCost === 'Free' ? 'Free' : listing.shippingCost}
</span>
)}
</div>
<ExternalLink className="h-3 w-3 text-yellow-700 dark:text-yellow-300 group-hover:text-yellow-900 dark:group-hover:text-yellow-100 transition-colors flex-shrink-0" />
</div>
{listing.condition && (
<div className="text-[9px] text-yellow-700/80 dark:text-yellow-300/80">
{listing.condition}
</div>
)}
</div>
</div>
</a>
))}
</div>
)}
{/* View All Listings Button */}
{part.ebayListings.length > 2 && (
<div className="pt-1">
<Button
variant="ghost"
size="xs"
className="w-full text-[10px] text-yellow-700 dark:text-yellow-300 hover:text-yellow-900 dark:hover:text-yellow-100"
>
View All {part.ebayListings.length} Listings
</Button>
</div>
)}
</div>
))}
{/* View All Parts Button */}
{parts.length > 3 && (
<div className="pt-2 pb-3">
<Button
variant="ghost"
size="sm"
className="w-full text-sm text-yellow-700 dark:text-yellow-300 hover:text-yellow-900 dark:hover:text-yellow-100"
>
View All Parts
</Button>
</div>
)}
</div>
</div>
)
}