ar-test/app/routes/admin/asset-preview.tsx

192 lines
9.6 KiB
TypeScript

'use client';
import { useEffect, useState, useRef } from 'react';
import { useLoaderData, Link, redirect, useNavigate } from 'react-router';
import type { Route } from './+types/asset-preview';
import { pb } from '~/lib/pocketbase';
import { Button } from '~/components/ui/button';
import { Badge } from '~/components/ui/badge';
import { Card } from '~/components/ui/card';
import { Download, ImageOff, FileCode, ArrowLeft, Box } from 'lucide-react';
import { toast } from 'sonner';
export async function clientLoader({ params }: Route.ClientLoaderArgs) {
if (!pb.authStore.isValid) {
return redirect('/login');
}
try {
const asset = await pb.collection('ar_assets').getOne(params.id);
return { asset, error: null };
} catch (err: any) {
console.error('Failed to load asset in clientLoader:', err);
return { asset: null, error: err?.message || 'Failed to fetch asset details' };
}
}
export default function Preview() {
const { asset, error } = useLoaderData<typeof clientLoader>();
const navigate = useNavigate();
useEffect(() => {
if (error || !asset) {
toast.error(error || 'Asset not found');
navigate('/', { replace: true });
}
}, [error, asset, navigate]);
if (!asset) return null;
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;
const downloadQR = async () => {
const qrUrl = asset.qr_image
? pb.files.getURL(asset, asset.qr_image)
: asset.qr_file
? pb.files.getURL(asset, asset.qr_file)
: null;
if (!qrUrl) {
toast.error('No QR code image found for this asset');
return;
}
try {
const response = await fetch(qrUrl);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${asset.name.replace(/\s+/g, '_')}_qr.png`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
toast.success('QR Code downloaded successfully');
} catch (err) {
console.error('Failed to download QR code:', err);
toast.error('Failed to download QR code');
}
};
return (
<div className="min-h-screen bg-[#0D0D0D] text-white px-4 py-8 md:p-10 font-sans selection:bg-orange-500/20">
<div className="mx-auto max-w-5xl space-y-6">
<div className="flex items-center justify-between pb-4 border-b border-neutral-900">
<Link
to="/"
className="inline-flex items-center gap-2 text-xs font-bold text-neutral-400 hover:text-orange-500 transition-colors uppercase tracking-wider cursor-pointer"
>
<ArrowLeft size={16} />
<span>Back to Dashboard</span>
</Link>
<div className="w-8 h-8 bg-black border border-neutral-850 rounded-xl flex items-center justify-center shrink-0">
<span className="text-orange-500 font-extrabold text-lg pl-0.5 select-none">t.</span>
</div>
</div>
<Card className="flex flex-col lg:flex-row gap-6 overflow-hidden shadow-2xl border-neutral-900">
<div className="flex-grow lg:flex-1 min-h-[350px] sm:min-h-[450px] lg:min-h-[600px] bg-black relative rounded-t-2xl lg:rounded-l-2xl lg:rounded-tr-none overflow-hidden">
{glbUrl ? (
<model-viewer
src={glbUrl}
ios-src={usdzUrl || undefined}
ar
ar-modes="webxr scene-viewer quick-look"
camera-controls
auto-rotate
shadow-intensity="1"
shadow-softness="0.8"
exposure="1.1"
style={{ width: '100%', height: '100%', display: 'block', position: 'absolute', top: 0, left: 0 }}
className="w-full h-full"
alt={asset.name}
/>
) : (
<div className="w-full h-full flex flex-col items-center justify-center text-neutral-500 p-8">
<FileCode size={40} className="mb-2 text-neutral-600 animate-pulse" />
<span className="text-xs uppercase tracking-widest text-neutral-600">Initializing 3D viewport</span>
</div>
)}
</div>
<div className="w-full lg:w-[360px] bg-[#121212] p-6 sm:p-8 flex flex-col justify-between shrink-0 border-t lg:border-t-0 lg:border-l border-neutral-850">
<div className="space-y-6">
<div>
<Badge variant="orange" className="mb-2">Asset Details</Badge>
<h3 className="text-xl font-bold text-white leading-snug">{asset.name}</h3>
</div>
<div className="bg-white p-4 rounded-xl flex items-center justify-center max-w-[210px] mx-auto shadow-md border border-neutral-200">
{asset.qr_image ? (
<img
src={pb.files.getURL(asset, asset.qr_image)}
alt={asset.name}
className="w-full aspect-square rounded-lg object-contain"
/>
) : asset.qr_file ? (
<img
src={pb.files.getURL(asset, asset.qr_file)}
alt={asset.name}
className="w-full aspect-square rounded-lg object-contain"
/>
) : (
<div className="flex aspect-square w-full flex-col items-center justify-center text-neutral-400 bg-neutral-100 rounded-lg p-4">
<ImageOff size={32} />
<span className="text-[10px] mt-1 font-semibold uppercase">No QR code</span>
</div>
)}
</div>
<p className="text-xs text-neutral-400 text-center leading-relaxed px-2">
Scan this QR code with a smartphone camera to instantly project the 3D model in your space using AR.
</p>
<div className="border-t border-neutral-850 pt-5 space-y-3">
<div>
<span className="text-[10px] font-bold text-neutral-500 uppercase tracking-wider">GLB File Target</span>
<div className="text-xs font-mono text-neutral-300 truncate mt-1 bg-neutral-900 px-3 py-2 rounded-lg border border-neutral-850 select-all">
{glbUrl}
</div>
</div>
{usdzUrl && (
<div>
<span className="text-[10px] font-bold text-neutral-500 uppercase tracking-wider">USDZ File Target</span>
<div className="text-xs font-mono text-neutral-300 truncate mt-1 bg-neutral-900 px-3 py-2 rounded-lg border border-neutral-850 select-all">
{usdzUrl}
</div>
</div>
)}
</div>
</div>
<div className="space-y-3 mt-8">
<Button
onClick={downloadQR}
variant="outline"
className="w-full py-6 text-xs uppercase tracking-wider"
>
<Download size={15} className="text-orange-500" />
<span>Download QR Code</span>
</Button>
<a
href={`${import.meta.env.VITE_FRONTEND_URL}/ar/${asset.id}`}
target="_blank"
rel="noopener noreferrer"
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl bg-orange-500 hover:bg-orange-400 text-black transition-all font-bold text-xs uppercase tracking-wider shadow-[0_0_15px_rgba(249,115,22,0.15)]"
>
<Box size={14} />
<span>Launch AR View</span>
</a>
</div>
</div>
</Card>
</div>
</div>
);
}