All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 1m10s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 3s
Build & Deploy Costco Grocery List / deploy (push) Successful in 11s
Build & Deploy Costco Grocery List / notify (push) Successful in 1s
93 lines
3.0 KiB
JavaScript
93 lines
3.0 KiB
JavaScript
import useUploadQueue from "../../hooks/useUploadQueue";
|
|
import useActionToast from "../../hooks/useActionToast";
|
|
import "../../styles/components/UploadToaster.css";
|
|
|
|
function getStatusLabel(upload, isOnline) {
|
|
if (upload.status === "uploading") {
|
|
return `Uploading... ${upload.progress || 0}%`;
|
|
}
|
|
if (upload.status === "success") {
|
|
return "Upload complete";
|
|
}
|
|
if (upload.status === "queued") {
|
|
return isOnline ? "Queued for upload..." : "Waiting for network...";
|
|
}
|
|
return upload.lastError || "Upload failed. Retry or discard.";
|
|
}
|
|
|
|
export default function UploadToaster() {
|
|
const { uploads, isOnline, retryUpload, discardUpload } = useUploadQueue();
|
|
const { toasts, dismiss } = useActionToast();
|
|
|
|
if (!uploads.length && !toasts.length) {
|
|
return null;
|
|
}
|
|
|
|
const sortedToasts = [...toasts].sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
|
|
const sortedUploads = [...uploads].sort(
|
|
(a, b) => (b.updatedAt || b.createdAt || 0) - (a.updatedAt || a.createdAt || 0)
|
|
);
|
|
|
|
return (
|
|
<div className="upload-toaster" aria-live="polite" aria-atomic="false">
|
|
{sortedToasts.map((toast) => (
|
|
<div
|
|
key={toast.id}
|
|
className={`upload-toast action-toast action-toast-${toast.variant}`}
|
|
role={toast.variant === "error" ? "alert" : "status"}
|
|
>
|
|
<div className="action-toast-header">
|
|
<div className="upload-toast-title">{toast.title}</div>
|
|
<button
|
|
type="button"
|
|
onClick={() => dismiss(toast.id)}
|
|
className="action-toast-close"
|
|
aria-label="Dismiss notification"
|
|
>
|
|
x
|
|
</button>
|
|
</div>
|
|
{toast.message ? <div className="upload-toast-status">{toast.message}</div> : null}
|
|
</div>
|
|
))}
|
|
|
|
{sortedUploads.map((upload) => (
|
|
<div
|
|
key={upload.id}
|
|
className={`upload-toast upload-toast-${upload.status}`}
|
|
role="status"
|
|
>
|
|
<div className="upload-toast-title">{upload.itemName}</div>
|
|
<div className="upload-toast-status">{getStatusLabel(upload, isOnline)}</div>
|
|
|
|
<div className="upload-toast-progress">
|
|
<div
|
|
className="upload-toast-progress-fill"
|
|
style={{ width: `${upload.status === "success" ? 100 : upload.progress || 0}%` }}
|
|
/>
|
|
</div>
|
|
|
|
{upload.status === "failed" && (
|
|
<div className="upload-toast-actions">
|
|
<button type="button" onClick={() => retryUpload(upload.id)}>
|
|
Retry
|
|
</button>
|
|
<button type="button" onClick={() => discardUpload(upload.id)}>
|
|
Discard
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{upload.status === "queued" && (
|
|
<div className="upload-toast-actions">
|
|
<button type="button" onClick={() => discardUpload(upload.id)}>
|
|
Discard
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|