/
t3
/
web-ui
Обзор
Документация
Войти
/
t3
/
web-ui
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
src/features/buckets/CreateBucketDialog.tsx
120 строк
5 KB
Ivan Shibkikh
s3 integration
23 июл 2026, 21:55
23 июл 2026, 21:55
2235594
Код
Авторство
О чём код?
import { useState } 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 { bucketsApi, ApiError } from "@/api/client"; import { useMutation, useQueryClient } from "@tanstack/react-query"; interface CreateBucketDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } const BUCKET_NAME_RE = /^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/; export function CreateBucketDialog({ open, onOpenChange }: CreateBucketDialogProps) { const [name, setName] = useState(""); const [error, setError] = useState<string | null>(null); const [validationError, setValidationError] = useState<string | null>(null); const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: (bucketName: string) => bucketsApi.create({ name: bucketName }), onSuccess: () => { setName(""); setError(null); setValidationError(null); queryClient.invalidateQueries({ queryKey: ["buckets"] }); onOpenChange(false); }, onError: (err) => { if (err instanceof ApiError) { setError(err.detail ?? err.message); } else { setError(err instanceof Error ? err.message : "Ошибка создания бакета"); } }, }); const validate = (value: string): string | null => { if (value.length < 3 || value.length > 63) { return "Имя бакета должно быть от 3 до 63 символов"; } if (!BUCKET_NAME_RE.test(value)) { return "Имя бакета может содержать только строчные буквы, цифры, точки и дефисы"; } if (value.includes("..")) { return "Имя бакета не может содержать две точки подряд"; } return null; }; const handleNameChange = (value: string) => { const normalized = value.toLowerCase().replace(/[^a-z0-9.-]/g, ""); setName(normalized); setValidationError(validate(normalized)); setError(null); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const ve = validate(name); if (ve) { setValidationError(ve); return; } mutation.mutate(name.trim()); }; const handleClose = () => { setName(""); setError(null); setValidationError(null); mutation.reset(); onOpenChange(false); }; return ( <Dialog open={open} onOpenChange={handleClose}> <DialogContent className="sm:max-w-md"> <DialogHeader> <DialogTitle>Создать бакет</DialogTitle> <DialogDescription> Имя бакета должно быть уникальным в системе. Допустимы строчные буквы, цифры, точки и дефисы (3–63 символа). </DialogDescription> </DialogHeader> <form onSubmit={handleSubmit} className="space-y-4"> <div className="space-y-2"> <Label htmlFor="bucket_name">Имя бакета</Label> <Input id="bucket_name" placeholder="my-bucket" value={name} onChange={(e) => handleNameChange(e.target.value)} autoFocus /> {validationError && ( <p className="text-sm text-destructive">{validationError}</p> )} </div> {error && ( <div className="rounded-md bg-destructive/10 p-3 text-sm text-destructive"> {error} </div> )} <div className="flex justify-end gap-2"> <Button type="button" variant="outline" onClick={handleClose}> Отмена </Button> <Button type="submit" disabled={mutation.isPending || !!validationError}> {mutation.isPending ? "Создание..." : "Создать"} </Button> </div> </form> </DialogContent> </Dialog> ); }