From 29f002650fefcd80092f6196c95ffe788b57e8bf Mon Sep 17 00:00:00 2001 From: rajun Date: Mon, 20 Jul 2026 18:04:47 +0530 Subject: [PATCH] feat: enable direct file uploads for AR assets --- app/routes/home.tsx | 227 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 192 insertions(+), 35 deletions(-) diff --git a/app/routes/home.tsx b/app/routes/home.tsx index c9e26a0..cdf8e1b 100644 --- a/app/routes/home.tsx +++ b/app/routes/home.tsx @@ -1,14 +1,27 @@ 'use client'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import QRCode from 'qrcode'; import { pb } from '~/lib/pocketbase'; -import { ImageOff } from 'lucide-react'; +import { ImageOff, Upload, Link, X } from 'lucide-react'; + +type InputMode = 'url' | 'file'; export default function Dashboard() { const [name, setName] = useState(''); + + // GLB + const [glbMode, setGlbMode] = useState('file'); const [glbUrl, setGlbUrl] = useState(''); + const [glbFile, setGlbFile] = useState(null); + const glbInputRef = useRef(null); + + // USDZ + const [usdzMode, setUsdzMode] = useState('file'); const [usdzUrl, setUsdzUrl] = useState(''); + const [usdzFile, setUsdzFile] = useState(null); + const usdzInputRef = useRef(null); + const [assets, setAssets] = useState([]); const [loading, setLoading] = useState(false); @@ -29,19 +42,32 @@ export default function Dashboard() { }, []); const createAsset = async () => { - if (!name || !glbUrl) { - alert('Please provide a name and a GLB URL'); + const hasGlb = glbMode === 'file' ? !!glbFile : !!glbUrl.trim(); + if (!name.trim() || !hasGlb) { + alert('Please provide a name and a GLB file or URL'); return; } setLoading(true); try { - const record = await pb.collection('ar_assets').create({ - name, - glb_url: glbUrl, - usdz_url: usdzUrl || '', - }); + // Build FormData so we can send files + fields in one request + const formData = new FormData(); + formData.append('name', name.trim()); + + if (glbMode === 'file' && glbFile) { + formData.append('glb_file', glbFile); + } else { + formData.append('glb_url', glbUrl.trim()); + } + + if (usdzMode === 'file' && usdzFile) { + formData.append('usdz_file', usdzFile); + } else if (usdzMode === 'url' && usdzUrl.trim()) { + formData.append('usdz_url', usdzUrl.trim()); + } + + const record = await pb.collection('ar_assets').create(formData); const qrUrl = `${import.meta.env.VITE_FRONTEND_URL}/ar/${record.id}`; @@ -58,9 +84,14 @@ export default function Dashboard() { await pb.collection('ar_assets').update(record.id, qrFormData); + // Reset all fields setName(''); setGlbUrl(''); + setGlbFile(null); setUsdzUrl(''); + setUsdzFile(null); + if (glbInputRef.current) glbInputRef.current.value = ''; + if (usdzInputRef.current) usdzInputRef.current.value = ''; fetchAssets(); } catch (err: any) { @@ -83,13 +114,14 @@ export default function Dashboard() {
-

+

Create New Asset

-
-
- +
+ {/* Asset Name */} +
+ setName(e.target.value)} />
-
- - setGlbUrl(e.target.value)} - /> -
-
- - setUsdzUrl(e.target.value)} - /> -
+ + {/* GLB — Android / Web */} + + + {/* USDZ — iOS */} +
@@ -176,3 +222,114 @@ export default function Dashboard() { ); } +// ─── Reusable File-or-URL field ─────────────────────────────────────────────── +interface FileOrUrlFieldProps { + label: string; + required?: boolean; + mode: InputMode; + onModeChange: (m: InputMode) => void; + accept: string; + file: File | null; + onFileChange: (f: File | null) => void; + inputRef: React.RefObject; + url: string; + onUrlChange: (u: string) => void; + urlPlaceholder: string; +} + +function FileOrUrlField({ + label, required, mode, onModeChange, + accept, file, onFileChange, inputRef, + url, onUrlChange, urlPlaceholder, +}: FileOrUrlFieldProps) { + return ( +
+ {/* Label + toggle */} +
+ +
+ + +
+
+ + {mode === 'file' ? ( +
inputRef.current?.click()} + className={`flex cursor-pointer items-center gap-3 rounded-lg border-2 border-dashed px-4 py-3 transition-colors ${ + file ? 'border-black bg-gray-50' : 'border-gray-200 hover:border-gray-400' + }`} + > + onFileChange(e.target.files?.[0] ?? null)} + /> + {file ? ( + <> +
+
+ +
+ {file.name} + + {(file.size / 1024 / 1024).toFixed(1)} MB + +
+ + + ) : ( +
+ + Click to choose a file ({accept}) +
+ )} +
+ ) : ( + onUrlChange(e.target.value)} + /> + )} +
+ ); +}