'use client'; import { useEffect, useRef, useState } from 'react'; import { useParams } from 'react-router'; import { Box } from 'lucide-react'; import { pb } from '~/lib/pocketbase'; // --- A-FRAME/AR.JS CUSTOM ELEMENTS TYPING FIX --- declare module 'react' { namespace JSX { interface IntrinsicElements { 'a-scene': any; 'a-assets': any; 'a-asset-item': any; 'a-marker': any; 'a-entity': any; 'a-text': any; 'model-viewer': React.DetailedHTMLProps< React.HTMLAttributes, HTMLElement > & { src?: string; 'ios-src'?: string; ar?: boolean; 'ar-modes'?: string; 'ar-scale'?: string; 'ar-placement'?: string; 'camera-controls'?: boolean; 'touch-action'?: string; alt?: string; 'shadow-intensity'?: string | number; 'shadow-softness'?: string | number; exposure?: string | number; 'interaction-prompt'?: string; 'min-camera-orbit'?: string; 'max-camera-orbit'?: string; 'camera-orbit'?: string; 'field-of-view'?: string; scale?: string; 'auto-rotate'?: boolean; 'rotation-per-second'?: string; onLoad?: (e: any) => void; 'onAr-status'?: (e: any) => void; 'onModel-visibility'?: (e: any) => void; 'onCamera-change'?: (e: any) => void; }; } } } const AR_HINTS = [ 'Arranging models to view…', 'Find a flat surface & step back…', 'Keep your camera steady…', 'Almost ready, clear some space…', 'Preparing your AR experience…', ]; export default function ARViewer() { const { id } = useParams(); const modelViewerRef = useRef(null); const [asset, setAsset] = useState(null); const [scriptLoaded, setScriptLoaded] = useState(false); const [arLoading, setArLoading] = useState(false); const [showHints, setShowHints] = useState(false); const [hintIndex, setHintIndex] = useState(0); const hintIntervalRef = useRef | null>(null); const hintDelayRef = useRef | null>(null); useEffect(() => { if (!id) return; const loadAsset = async () => { const data = await pb.collection('ar_assets').getOne(id); if (data) setAsset(data); }; loadAsset(); }, [id]); useEffect(() => { if (document.getElementById('model-viewer-script')) { setScriptLoaded(true); return; } const script = document.createElement('script'); script.id = 'model-viewer-script'; script.type = 'module'; script.src = 'https://ajax.googleapis.com/ajax/libs/model-viewer/3.4.0/model-viewer.min.js'; script.onload = () => setScriptLoaded(true); document.head.appendChild(script); }, []); const handleARClick = () => { const mv = modelViewerRef.current as any; if (!mv?.activateAR || arLoading) return; setArLoading(true); setShowHints(false); setHintIndex(0); mv.activateAR(); const cleanup = () => { setArLoading(false); setShowHints(false); if (hintIntervalRef.current) clearInterval(hintIntervalRef.current); if (hintDelayRef.current) clearTimeout(hintDelayRef.current); }; // Only show hints if AR is still loading after 1.5s hintDelayRef.current = setTimeout(() => { setShowHints(true); hintIntervalRef.current = setInterval(() => { setHintIndex((prev) => (prev + 1) % AR_HINTS.length); }, 2000); }, 1500); const onArStatus = (e: any) => { const { status } = e.detail ?? {}; if ( status === 'session-started' || status === 'object-placed' || status === 'failed' || status === 'not-presenting' ) { cleanup(); mv.removeEventListener('ar-status', onArStatus); } }; mv.addEventListener('ar-status', onArStatus); // Fallback reset after 15s setTimeout(cleanup, 15000); }; const glbUrl = asset?.glb_file ? pb.files.getURL(asset, asset.glb_file) : asset?.glb_url; const usdzUrl = asset?.usdz_file ? pb.files.getURL(asset, asset.usdz_file) : asset?.usdz_url; if (!scriptLoaded || !asset) { return (

Loading AR Experience…

); } return (
{ console.error('Model viewer error:', e); console.error('Failed to load:', glbUrl); }} onLoad={() => console.log('Model loaded successfully')} />
{asset?.name && ( {asset.name} )}
t.
); }