refactor: migrate from supabase to pocketbase and improve dashboard UI
This commit is contained in:
parent
15a7cf3ac4
commit
979516b681
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
import { supabase } from '~/lib/supabase';
|
import { pb } from '~/lib/pocketbase';
|
||||||
|
import { ImageOff } from 'lucide-react';
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
@ -12,12 +13,15 @@ export default function Dashboard() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const fetchAssets = async () => {
|
const fetchAssets = async () => {
|
||||||
const { data } = await supabase
|
try {
|
||||||
.from('ar_assets')
|
const records = await pb.collection('ar_assets').getFullList({
|
||||||
.select('*')
|
sort: '-created',
|
||||||
.order('created_at', { ascending: false });
|
});
|
||||||
|
setAssets(records);
|
||||||
setAssets(data || []);
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch assets:', err);
|
||||||
|
setAssets([]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -25,62 +29,45 @@ export default function Dashboard() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const createAsset = async () => {
|
const createAsset = async () => {
|
||||||
if (!name || !glbUrl || !usdzUrl) {
|
if (!name || !glbUrl) {
|
||||||
alert('Fill all fields');
|
alert('Please provide a name and a GLB URL');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const { data, error } = await supabase
|
try {
|
||||||
.from('ar_assets')
|
const record = await pb.collection('ar_assets').create({
|
||||||
.insert({
|
|
||||||
name,
|
name,
|
||||||
glb_url: glbUrl,
|
glb_url: glbUrl,
|
||||||
usdz_url: usdzUrl,
|
usdz_url: usdzUrl || '',
|
||||||
})
|
});
|
||||||
.select()
|
|
||||||
.single();
|
|
||||||
|
|
||||||
if (error || !data) {
|
const qrUrl = `${import.meta.env.VITE_FRONTEND_URL}/ar/${record.id}`;
|
||||||
alert(error?.message);
|
|
||||||
|
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);
|
setLoading(false);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// const qrUrl = `http://10.20.2.107:5173/ar/${data.id}`;
|
|
||||||
const qrUrl = `${window.location.origin}/ar/${data.id}`;
|
|
||||||
|
|
||||||
const qrImage = await QRCode.toDataURL(qrUrl, {
|
|
||||||
width: 512,
|
|
||||||
margin: 2,
|
|
||||||
});
|
|
||||||
|
|
||||||
const qrBlob = await (await fetch(qrImage)).blob();
|
|
||||||
const filePath = `${data.id}.png`;
|
|
||||||
|
|
||||||
await supabase.storage.from('qr-codes').upload(filePath, qrBlob, {
|
|
||||||
upsert: true,
|
|
||||||
contentType: 'image/png',
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: publicUrl } = supabase.storage
|
|
||||||
.from('qr-codes')
|
|
||||||
.getPublicUrl(filePath);
|
|
||||||
|
|
||||||
await supabase
|
|
||||||
.from('ar_assets')
|
|
||||||
.update({
|
|
||||||
qr_url: qrUrl,
|
|
||||||
qr_image_url: publicUrl.publicUrl,
|
|
||||||
})
|
|
||||||
.eq('id', data.id);
|
|
||||||
|
|
||||||
setName('');
|
|
||||||
setGlbUrl('');
|
|
||||||
setUsdzUrl('');
|
|
||||||
setLoading(false);
|
|
||||||
fetchAssets();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -101,24 +88,35 @@ export default function Dashboard() {
|
|||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className='grid gap-4 md:grid-cols-3 text-black'>
|
<div className='grid gap-4 md:grid-cols-3 text-black'>
|
||||||
<input
|
<div className="space-y-1">
|
||||||
className='rounded-lg border border-gray-300 px-4 py-2 text-sm focus:border-black focus:outline-none'
|
<label className="text-xs font-semibold text-gray-600 ml-1">Asset Name</label>
|
||||||
placeholder='Asset name'
|
<input
|
||||||
value={name}
|
className='w-full rounded-lg border border-gray-300 px-4 py-2 text-sm focus:border-black focus:outline-none'
|
||||||
onChange={(e) => setName(e.target.value)}
|
placeholder='Asset name'
|
||||||
/>
|
value={name}
|
||||||
<input
|
onChange={(e) => setName(e.target.value)}
|
||||||
className='rounded-lg border border-gray-300 px-4 py-2 text-sm focus:border-black focus:outline-none'
|
/>
|
||||||
placeholder='GLB URL'
|
</div>
|
||||||
value={glbUrl}
|
<div className="space-y-1">
|
||||||
onChange={(e) => setGlbUrl(e.target.value)}
|
<label className="text-xs font-semibold text-gray-600 ml-1">GLB URL (Android/Web)</label>
|
||||||
/>
|
<input
|
||||||
<input
|
type="url"
|
||||||
className='rounded-lg border border-gray-300 px-4 py-2 text-sm focus:border-black focus:outline-none'
|
className='w-full rounded-lg border border-gray-300 px-4 py-2 text-sm focus:border-black focus:outline-none'
|
||||||
placeholder='USDZ URL'
|
placeholder='https://example.com/model.glb'
|
||||||
value={usdzUrl}
|
value={glbUrl}
|
||||||
onChange={(e) => setUsdzUrl(e.target.value)}
|
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>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -150,15 +148,16 @@ export default function Dashboard() {
|
|||||||
{asset?.name}
|
{asset?.name}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{asset?.qr_image_url ? (
|
{asset?.qr_image ? (
|
||||||
<img
|
<img
|
||||||
src={asset?.qr_image_url}
|
src={pb.files.getURL(asset, asset.qr_image)}
|
||||||
alt={asset?.name}
|
alt={asset?.name}
|
||||||
className='mx-auto w-40 rounded-lg'
|
className='mx-auto w-40 rounded-lg'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className='flex h-40 items-center justify-center rounded-lg bg-gray-100 text-sm text-gray-400'>
|
<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'>
|
||||||
No QR
|
<ImageOff size={32} strokeWidth={1.5} />
|
||||||
|
<span className='text-xs font-medium'>No QR Image</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -170,3 +169,4 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user