/
t3
/
web-ui
Обзор
Документация
Войти
/
t3
/
web-ui
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
src/features/buckets/ObjectUploadDialog.tsx
171 строка
6 KB
Ivan Shibkikh
s3 integration
23 июл 2026, 21:55
23 июл 2026, 21:55
2235594
Код
Авторство
О чём код?
import { useState, useRef } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { objectsApi, ApiError } from "@/api/client"; import { useQueryClient } from "@tanstack/react-query"; import { Upload, AlertTriangle } from "lucide-react"; interface ObjectUploadDialogProps { bucket: string; open: boolean; onOpenChange: (open: boolean) => void; } export function ObjectUploadDialog({ bucket, open, onOpenChange }: ObjectUploadDialogProps) { const queryClient = useQueryClient(); const fileInputRef = useRef<HTMLInputElement>(null); const [file, setFile] = useState<File | null>(null); const [key, setKey] = useState(""); const [uploading, setUploading] = useState(false); const [error, setError] = useState<string | null>(null); const [progress, setProgress] = useState<string | null>(null); const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { const f = e.target.files?.[0]; if (f) { setFile(f); if (!key) { setKey(f.name); } setError(null); setProgress(null); } }; const handleUpload = async () => { if (!file || !key.trim()) { setError("Выберите файл и укажите ключ"); return; } setUploading(true); setError(null); setProgress("Получение ссылки..."); try { const { data: presignData } = await objectsApi.presignUpload(bucket, key.trim()); if (!presignData.url) { throw new Error("Не удалось получить ссылку для загрузки"); } setProgress(`Загрузка ${file.name} (${formatFileSize(file.size)})...`); const uploadResp = await fetch(presignData.url, { method: "PUT", body: file, headers: { "Content-Type": file.type || "application/octet-stream", }, }); if (!uploadResp.ok) { const text = await uploadResp.text().catch(() => ""); throw new Error(`S3 вернул ошибку ${uploadResp.status}: ${text}`); } setProgress("Готово"); queryClient.invalidateQueries({ queryKey: ["buckets", bucket, "objects"] }); // Close after short delay so user sees "Готово" setTimeout(() => { handleClose(); }, 800); } catch (err) { if (err instanceof ApiError) { setError(err.detail ?? err.message); } else { setError(err instanceof Error ? err.message : "Ошибка загрузки"); } setProgress(null); } finally { setUploading(false); } }; const handleClose = () => { setFile(null); setKey(""); setError(null); setProgress(null); setUploading(false); if (fileInputRef.current) { fileInputRef.current.value = ""; } onOpenChange(false); }; return ( <Dialog open={open} onOpenChange={handleClose}> <DialogContent className="sm:max-w-md"> <DialogHeader> <DialogTitle>Загрузить файл</DialogTitle> <DialogDescription> Файл будет загружен в бакет <strong>{bucket}</strong> </DialogDescription> </DialogHeader> <div className="space-y-4"> <div className="space-y-2"> <Label htmlFor="object_key">Ключ (путь) объекта</Label> <Input id="object_key" placeholder="folder/file.txt" value={key} onChange={(e) => { setKey(e.target.value); setError(null); }} disabled={uploading} /> </div> <div className="space-y-2"> <Label>Файл</Label> <Input ref={fileInputRef} type="file" onChange={handleFileChange} disabled={uploading} /> {file && ( <p className="text-xs text-muted-foreground"> {file.name} — {formatFileSize(file.size)} </p> )} </div> {progress && !error && ( <div className="rounded-md bg-primary/10 p-3 text-sm text-primary"> {progress} </div> )} {error && ( <div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive flex items-start gap-2"> <AlertTriangle className="h-4 w-4 mt-0.5 shrink-0" /> <span>{error}</span> </div> )} <div className="flex justify-end gap-2"> <Button variant="outline" onClick={handleClose} disabled={uploading}> Отмена </Button> <Button onClick={handleUpload} disabled={uploading || !file}> <Upload className="h-4 w-4" /> {uploading ? "Загрузка..." : "Загрузить"} </Button> </div> </div> </DialogContent> </Dialog> ); } function formatFileSize(bytes: number): string { if (bytes === 0) return "0 Б"; const units = ["Б", "КБ", "МБ", "ГБ", "ТБ"]; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return `${(bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0)} ${units[i]}`; }