Defer user-owned S3 setup until first media upload

Hop-State: A_06FP6DP9ENXP2WBSR2YPNA8
Hop-Proposal: R_06FP6DM6S5P96EFX90PH2F8
Hop-Task: T_06FP6C52Z3JHQBQ5XNNHZ3R
Hop-Attempt: AT_06FP6C52Z14V6CZJQTJN4PR
This commit is contained in:
2026-07-14 17:39:22 -07:00
committed by Hop
parent 0ecb622565
commit 752298a405
10 changed files with 304 additions and 274 deletions
+30 -5
View File
@@ -7,6 +7,7 @@ import { ImageIcon, AlertTriangle, Film } from 'lucide-react';
import { VideoEmbed } from '@/components/VideoEmbed';
import { useFormattedHandle } from '@/lib/utils/handle';
import { useAuth } from '@/lib/contexts/AuthContext';
import { StorageConfigurationPrompt } from '@/components/StorageConfigurationPrompt';
interface MediaAttachment extends Attachment {
mimeType?: string;
@@ -28,6 +29,8 @@ export function Compose({ onPost, replyingTo, onCancelReply, placeholder = "What
const [attachments, setAttachments] = useState<MediaAttachment[]>([]);
const [isUploading, setIsUploading] = useState(false);
const [uploadError, setUploadError] = useState<string | null>(null);
const [pendingStorageFiles, setPendingStorageFiles] = useState<File[]>([]);
const [showStorageConfiguration, setShowStorageConfiguration] = useState(false);
const [linkPreview, setLinkPreview] = useState<any>(null);
const [fetchingPreview, setFetchingPreview] = useState(false);
const [lastDetectedUrl, setLastDetectedUrl] = useState<string | null>(null);
@@ -116,11 +119,7 @@ export function Compose({ onPost, replyingTo, onCancelReply, placeholder = "What
setIsPosting(false);
};
const handleMediaSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
event.target.value = '';
if (files.length === 0) return;
const uploadMediaFiles = async (files: File[]) => {
const remainingSlots = Math.max(0, 4 - attachments.length);
const selectedFiles = files.slice(0, remainingSlots);
if (selectedFiles.length === 0) return;
@@ -141,6 +140,12 @@ export function Compose({ onPost, replyingTo, onCancelReply, placeholder = "What
const data = await res.json();
if (!res.ok || !data.media?.id) {
if (data.code === 'STORAGE_NOT_CONFIGURED') {
setPendingStorageFiles(selectedFiles);
setShowStorageConfiguration(true);
setIsUploading(false);
return;
}
throw new Error(data.error || 'Upload failed');
}
@@ -160,12 +165,32 @@ export function Compose({ onPost, replyingTo, onCancelReply, placeholder = "What
setIsUploading(false);
};
const handleMediaSelect = async (event: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(event.target.files || []);
event.target.value = '';
if (files.length === 0) return;
await uploadMediaFiles(files);
};
const handleRemoveAttachment = (id: string) => {
setAttachments((prev) => prev.filter((item) => item.id !== id));
};
return (
<div className={`compose ${isReply ? 'reply-compose' : ''}`}>
<StorageConfigurationPrompt
open={showStorageConfiguration}
onConfigured={async () => {
setShowStorageConfiguration(false);
const files = pendingStorageFiles;
setPendingStorageFiles([]);
await uploadMediaFiles(files);
}}
onCancel={() => {
setShowStorageConfiguration(false);
setPendingStorageFiles([]);
}}
/>
{replyingTo && !isReply && (
<div className="compose-reply-target">
<div className="compose-reply-info">