ar-test/app/routes/home.tsx

173 lines
7.0 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import QRCode from 'qrcode';
import { pb } from '~/lib/pocketbase';
import { ImageOff } from 'lucide-react';
export default function Dashboard() {
const [name, setName] = useState('');
const [glbUrl, setGlbUrl] = useState('');
const [usdzUrl, setUsdzUrl] = useState('');
const [assets, setAssets] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const fetchAssets = async () => {
try {
const records = await pb.collection('ar_assets').getFullList({
sort: '-created',
});
setAssets(records);
} catch (err) {
console.error('Failed to fetch assets:', err);
setAssets([]);
}
};
useEffect(() => {
fetchAssets();
}, []);
const createAsset = async () => {
if (!name || !glbUrl) {
alert('Please provide a name and a GLB URL');
return;
}
setLoading(true);
try {
const record = await pb.collection('ar_assets').create({
name,
glb_url: glbUrl,
usdz_url: usdzUrl || '',
});
const qrUrl = `${import.meta.env.VITE_FRONTEND_URL}/ar/${record.id}`;
const qrImage = await QRCode.toDataURL(qrUrl, {
width: 512,
margin: 2,
});
const qrBlob = await (await fetch(qrImage)).blob();
const qrFormData = new FormData();
qrFormData.append('qr_url', qrUrl);
qrFormData.append('qr_image', new File([qrBlob], `${record.id}.png`, { type: 'image/png' }));
await pb.collection('ar_assets').update(record.id, qrFormData);
setName('');
setGlbUrl('');
setUsdzUrl('');
fetchAssets();
} catch (err: any) {
alert(err?.message || 'Failed to create asset');
} finally {
setLoading(false);
}
};
return (
<div className='min-h-screen bg-gray-50 px-6 py-10'>
<div className='mx-auto max-w-5xl space-y-10'>
<div>
<h1 className='text-3xl font-bold text-gray-900'>
AR Asset Dashboard
</h1>
<p className='mt-1 text-gray-500'>
Manage 3D assets and generate AR QR codes
</p>
</div>
<div className='rounded-2xl bg-white p-6 shadow-sm'>
<h2 className='mb-4 text-lg font-semibold text-gray-800'>
Create New Asset
</h2>
<div className='grid gap-4 md:grid-cols-3 text-black'>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-600 ml-1">Asset Name</label>
<input
className='w-full rounded-lg border border-gray-300 px-4 py-2 text-sm focus:border-black focus:outline-none'
placeholder='Asset name'
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-600 ml-1">GLB URL (Android/Web)</label>
<input
type="url"
className='w-full rounded-lg border border-gray-300 px-4 py-2 text-sm focus:border-black focus:outline-none'
placeholder='https://example.com/model.glb'
value={glbUrl}
onChange={(e) => setGlbUrl(e.target.value)}
/>
</div>
<div className="space-y-1">
<label className="text-xs font-semibold text-gray-600 ml-1">USDZ URL (iOS) - Optional</label>
<input
type="url"
className='w-full rounded-lg border border-gray-300 px-4 py-2 text-sm focus:border-black focus:outline-none'
placeholder='USDZ URL'
value={usdzUrl}
onChange={(e) => setUsdzUrl(e.target.value)}
/>
</div>
</div>
<button
onClick={createAsset}
disabled={loading}
className='mt-6 inline-flex items-center justify-center rounded-xl bg-black px-6 py-2.5 text-sm font-medium text-white transition hover:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-60'
>
{loading ? 'Creating…' : 'Create Asset'}
</button>
</div>
<div>
<h2 className='mb-4 text-lg font-semibold text-gray-800'>
Generated Assets
</h2>
{assets.length === 0 ? (
<p className='text-sm text-gray-500'>
No assets created yet.
</p>
) : (
<div className='grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3'>
{assets.map((asset) => (
<div
key={asset.id}
className='rounded-2xl bg-white p-5 shadow-sm transition hover:shadow-md'
>
<h3 className='mb-3 text-base font-semibold text-gray-900'>
{asset?.name}
</h3>
{asset?.qr_image ? (
<img
src={pb.files.getURL(asset, asset.qr_image)}
alt={asset?.name}
className='mx-auto w-40 rounded-lg'
/>
) : (
<div className='mx-auto flex h-40 w-40 flex-col items-center justify-center space-y-2 rounded-lg bg-gray-50 text-gray-400'>
<ImageOff size={32} strokeWidth={1.5} />
<span className='text-xs font-medium'>No QR Image</span>
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
</div>
);
}