feat(assets): add image upload state management and update button behavior during upload

This commit is contained in:
Yusuf İpek
2026-04-14 13:41:41 +03:00
parent d1b2d23509
commit 8a12bb484b
2 changed files with 28 additions and 22 deletions
+19 -19
View File
@@ -37,38 +37,38 @@ export async function GET(
// Parallelize the DB lookup and session check to narrow the timing delta // Parallelize the DB lookup and session check to narrow the timing delta
// between "asset not found" and "asset found, access denied" responses. // between "asset not found" and "asset found, access denied" responses.
const imageUrl = `/api/upload/image/${filename}`; const imageUrl = `/api/upload/image/${filename}`;
const [comment, session] = await Promise.all([ const projectSelect = {
db.comment.findFirst({
where: { imageUrl },
select: {
version: {
select: {
video: {
select: {
id: true,
projectId: true,
project: {
select: {
id: true, id: true,
ownerId: true, ownerId: true,
workspaceId: true, workspaceId: true,
visibility: true, visibility: true,
} as const;
const videoSelect = {
id: true,
projectId: true,
project: { select: projectSelect },
} as const;
const [comment, videoAsset, session] = await Promise.all([
db.comment.findFirst({
where: { imageUrl },
select: {
version: {
select: { video: { select: videoSelect } },
}, },
}, },
}, }),
}, db.videoAsset.findFirst({
}, where: { sourceUrl: imageUrl },
}, select: { video: { select: videoSelect } },
},
}), }),
auth(), auth(),
]); ]);
if (!comment) { const video = comment?.version?.video ?? videoAsset?.video ?? null;
if (!video) {
return apiErrors.forbidden('Access denied'); return apiErrors.forbidden('Access denied');
} }
const { video } = comment.version;
const access = await checkProjectAccess(video.project, session?.user?.id); const access = await checkProjectAccess(video.project, session?.user?.id);
if (!access.hasAccess) { if (!access.hasAccess) {
+9 -3
View File
@@ -109,6 +109,7 @@ export const AssetsPane = memo(function AssetsPane({
const [youtubeUrl, setYoutubeUrl] = useState(''); const [youtubeUrl, setYoutubeUrl] = useState('');
const [youtubeTitle, setYoutubeTitle] = useState(''); const [youtubeTitle, setYoutubeTitle] = useState('');
const [bunnyTitle, setBunnyTitle] = useState(''); const [bunnyTitle, setBunnyTitle] = useState('');
const [isUploadingImage, setIsUploadingImage] = useState(false);
const [isUploadingBunny, setIsUploadingBunny] = useState(false); const [isUploadingBunny, setIsUploadingBunny] = useState(false);
const [bunnyProgress, setBunnyProgress] = useState(0); const [bunnyProgress, setBunnyProgress] = useState(0);
const [bunnyProcessingByAssetId, setBunnyProcessingByAssetId] = useState<Record<string, boolean>>({}); const [bunnyProcessingByAssetId, setBunnyProcessingByAssetId] = useState<Record<string, boolean>>({});
@@ -290,6 +291,7 @@ export const AssetsPane = memo(function AssetsPane({
return; return;
} }
setIsUploadingImage(true);
try { try {
const formData = new FormData(); const formData = new FormData();
formData.append('image', file); formData.append('image', file);
@@ -319,6 +321,8 @@ export const AssetsPane = memo(function AssetsPane({
} catch (error) { } catch (error) {
console.error('Failed to upload image asset:', error); console.error('Failed to upload image asset:', error);
toast.error('Failed to upload image'); toast.error('Failed to upload image');
} finally {
setIsUploadingImage(false);
} }
}, [videoId, getGuestUploadToken, createAsset, imageTitle]); }, [videoId, getGuestUploadToken, createAsset, imageTitle]);
@@ -807,7 +811,7 @@ export const AssetsPane = memo(function AssetsPane({
<Button <Button
variant="outline" variant="outline"
className="w-full" className="w-full"
disabled={isCreatingAsset} disabled={isUploadingImage || isCreatingAsset}
onClick={() => { onClick={() => {
if (pendingImageFile) { if (pendingImageFile) {
void handleImageUpload(pendingImageFile); void handleImageUpload(pendingImageFile);
@@ -816,8 +820,10 @@ export const AssetsPane = memo(function AssetsPane({
imageInputRef.current?.click(); imageInputRef.current?.click();
}} }}
> >
<UploadCloud className="h-4 w-4 mr-2" /> {isUploadingImage || isCreatingAsset
{pendingImageFile ? 'Upload Image' : 'Select Image'} ? <Loader2 className="h-4 w-4 mr-2 animate-spin" />
: <UploadCloud className="h-4 w-4 mr-2" />}
{isUploadingImage ? 'Uploading...' : isCreatingAsset ? 'Saving...' : pendingImageFile ? 'Upload Image' : 'Select Image'}
</Button> </Button>
<input <input
ref={imageInputRef} ref={imageInputRef}