mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
change things
This commit is contained in:
@@ -5,19 +5,17 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { toast } from "sonner";
|
||||
import { ArrowLeft, ArrowRight, UploadCloud, X, FileVideo, FileText, Image, FileAudio } from "lucide-react";
|
||||
|
||||
import { useAssets } from "@/contexts/AdminAssetsContext";
|
||||
import { useUploadQueue } from "@/contexts/UploadQueueContext";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { MAX_ASSET_FILE_SIZE_SINGLE, MAX_ASSET_FILE_SIZE_SINGLE_LABEL } from "@/utils/assetUpload.util";
|
||||
import { formatFileSize } from "@/utils/format.util";
|
||||
import { useUnsavedChangesGuard } from "@/hooks/useUnsavedChangesGuard";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -39,8 +37,6 @@ function resolveFileType(mimeType = "") {
|
||||
// ─── Schema ───────────────────────────────────────────────────────────────────
|
||||
|
||||
const schema = z.object({
|
||||
display_name: z.string().min(1, "Display name is required."),
|
||||
description: z.string().optional(),
|
||||
is_public: z.enum(["true", "false"]),
|
||||
storage_provider: z.enum(["chibisafe", "local", "s3"]),
|
||||
});
|
||||
@@ -119,16 +115,15 @@ function FieldError({ message }) {
|
||||
|
||||
export default function AddAsset() {
|
||||
const navigate = useNavigate();
|
||||
const { uploadAsset, loading } = useAssets();
|
||||
const { fetchAssets } = useAssets();
|
||||
const { addBatch } = useUploadQueue();
|
||||
const { user } = useAuth();
|
||||
|
||||
const fileRef = useRef(null);
|
||||
const thumbnailRef = useRef(null);
|
||||
const [thumbKey, setThumbKey] = useState(0);
|
||||
const [progress, setProgress] = useState(null); // { phase: 'uploading'|'processing'|'done'|'error', pct } | null
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
@@ -139,8 +134,6 @@ export default function AddAsset() {
|
||||
} = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
display_name: "",
|
||||
description: "",
|
||||
is_public: "false",
|
||||
storage_provider: "s3", // ← changed from "chibisafe"
|
||||
},
|
||||
@@ -164,7 +157,6 @@ export default function AddAsset() {
|
||||
}
|
||||
fileRef.current = f;
|
||||
setValue("_file", f);
|
||||
if (!watch("display_name")) setValue("display_name", f.name);
|
||||
clearErrors("_file");
|
||||
};
|
||||
|
||||
@@ -174,40 +166,46 @@ export default function AddAsset() {
|
||||
clearErrors("_thumbnail");
|
||||
};
|
||||
|
||||
const onSubmit = async (data) => {
|
||||
let hasFileError = false;
|
||||
|
||||
// Fire-and-forget: the job goes on the same global UploadQueueContext the
|
||||
// Bulk flow uses, so it keeps uploading in the background no matter where
|
||||
// you navigate to next — a single sonner toast.promise() tracks it
|
||||
// through loading -> success/error instead of a dedicated widget.
|
||||
const onSubmit = (data) => {
|
||||
if (!fileRef.current) {
|
||||
setError("_file", { message: "A file is required." });
|
||||
hasFileError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasFileError) return;
|
||||
|
||||
setProgress({ phase: "uploading", pct: 0 });
|
||||
const result = await uploadAsset({
|
||||
file: fileRef.current,
|
||||
thumbnail: thumbnailRef.current ?? undefined,
|
||||
display_name: data.display_name,
|
||||
description: data.description ?? "",
|
||||
file_type: fileType,
|
||||
is_public: data.is_public === "true",
|
||||
storage_provider: data.storage_provider,
|
||||
createdBy: user?.user_id,
|
||||
onProgress: setProgress,
|
||||
const fileName = fileRef.current.name;
|
||||
const uploadPromise = new Promise((resolve, reject) => {
|
||||
addBatch(
|
||||
[{ file: fileRef.current, thumbnail: thumbnailRef.current ?? undefined }],
|
||||
{
|
||||
is_public: data.is_public === "true",
|
||||
storage_provider: data.storage_provider,
|
||||
createdBy: user?.user_id,
|
||||
},
|
||||
{
|
||||
source: "single",
|
||||
onSettled: ([result]) => {
|
||||
fetchAssets({ force: true });
|
||||
if (result?.status === "uploaded") resolve(result);
|
||||
else reject(new Error(result?.error || "Upload failed."));
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
setProgress(null);
|
||||
|
||||
if (result) { bypassOnce(); navigate("/admin/assets"); }
|
||||
toast.promise(uploadPromise, {
|
||||
loading: `Uploading ${fileName}…`,
|
||||
success: (result) => `${result.name} uploaded successfully.`,
|
||||
error: (err) => err.message || "Upload failed.",
|
||||
});
|
||||
|
||||
bypassOnce();
|
||||
navigate("/admin/assets");
|
||||
};
|
||||
|
||||
const progressLabel = {
|
||||
uploading: "Uploading…",
|
||||
processing: "Processing…",
|
||||
done: "Done.",
|
||||
error: "Upload failed.",
|
||||
}[progress?.phase];
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 py-6 space-y-6">
|
||||
|
||||
@@ -217,8 +215,8 @@ export default function AddAsset() {
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">Add Asset</h1>
|
||||
<p className="text-sm text-muted-foreground">Upload a new file to the asset library.</p>
|
||||
<h1 className="text-xl font-semibold">Add File</h1>
|
||||
<p className="text-sm text-muted-foreground">Upload a new file to the file library.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -236,7 +234,6 @@ export default function AddAsset() {
|
||||
onClear={() => {
|
||||
fileRef.current = null;
|
||||
setValue("_file", null);
|
||||
setValue("display_name", "");
|
||||
clearErrors("_file");
|
||||
}}
|
||||
error={errors._file?.message}
|
||||
@@ -277,30 +274,6 @@ export default function AddAsset() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Display Name ── */}
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="display_name">
|
||||
Display Name <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="display_name"
|
||||
placeholder="Friendly name for this asset"
|
||||
{...register("display_name")}
|
||||
/>
|
||||
<FieldError message={errors.display_name?.message} />
|
||||
</div>
|
||||
|
||||
{/* ── Description ── */}
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
placeholder="Optional description"
|
||||
rows={3}
|
||||
{...register("description")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* ── File Type · Access · Storage (one row) ── */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
|
||||
@@ -355,29 +328,12 @@ export default function AddAsset() {
|
||||
|
||||
</div>
|
||||
|
||||
{/* ── Upload progress (real — see AdminAssetsContext.uploadAsset) ── */}
|
||||
{progress && (
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>{progressLabel}</span>
|
||||
<span>{progress.pct ?? 0}%</span>
|
||||
</div>
|
||||
<Progress value={progress.pct ?? 0} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Actions ── */}
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => navigate("/admin/assets")}
|
||||
disabled={loading}
|
||||
>
|
||||
<Button type="button" variant="outline" onClick={() => navigate("/admin/assets")}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading && <Spinner className="h-4 w-4 mr-2" />}
|
||||
<Button type="submit">
|
||||
Upload Asset
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user