feat: implement upload error handling and rate limiting improvements

This commit is contained in:
2026-01-16 11:23:14 +01:00
parent e90c4576a5
commit 1d75df2d41
5 changed files with 94 additions and 27 deletions

View File

@@ -25,6 +25,7 @@
</div>
</div>
</div>
<div id="uploadError" class="upload-error" style="display: none" role="status" aria-live="polite"></div>
<div class="duration-container">
<label for="durationSlider">Duration: <span id="durationValue">5</span> min</label>
@@ -72,6 +73,17 @@
const linkContainer = document.getElementById("linkContainer");
const uploadedLink = document.getElementById("uploadedLink");
const clipboardMessage = document.getElementById("clipboardMessage");
const uploadError = document.getElementById("uploadError");
function formatRetryAfter(seconds) {
const safeSeconds = Math.max(0, Math.floor(seconds));
const minutes = Math.floor(safeSeconds / 60);
const remainder = safeSeconds % 60;
if (minutes > 0) {
return `${minutes}m ${remainder}s`;
}
return `${remainder}s`;
}
// Update duration display
durationSlider.addEventListener("input", function () {
@@ -115,6 +127,8 @@
};
try {
uploadError.style.display = "none";
uploadError.textContent = "";
const response = await fetch("/api/upload", {
method: "POST",
headers: {
@@ -123,10 +137,33 @@
body: JSON.stringify(payload),
});
const result = await response.json();
console.log(
`✅ Upload received!\n${JSON.stringify(result, null, 2)}`
);
let result = null;
try {
result = await response.json();
} catch (parseError) {
result = null;
}
if (!response.ok) {
const retryAfterSeconds = result && Number.isFinite(Number(result.retry_after_seconds))
? Number(result.retry_after_seconds)
: null;
let errorMessage =
(result && result.error) ||
`Upload failed (${response.status})`;
if (retryAfterSeconds !== null) {
errorMessage += ` Try again in ${formatRetryAfter(retryAfterSeconds)}.`;
}
uploadError.textContent = errorMessage;
uploadError.style.display = "block";
return;
}
if (!result || !result.link) {
uploadError.textContent = "Upload failed (invalid response)";
uploadError.style.display = "block";
return;
}
// Hide duration controls and buttons
document.querySelector('label[for="durationSlider"]').style.display =
@@ -175,7 +212,8 @@
};
}
} catch (error) {
console.log(`❌ Error: ${error.message}`);
uploadError.textContent = `Upload failed (${error.message})`;
uploadError.style.display = "block";
}
}
@@ -201,6 +239,8 @@
resetBtn.style.display = "none";
linkContainer.style.display = "none";
clipboardMessage.style.display = "none";
uploadError.style.display = "none";
uploadError.textContent = "";
uploadZone.focus();
});

View File

@@ -91,6 +91,18 @@ h1 .home-link:hover {
transition: all 0.3s ease;
}
.upload-error {
margin: 12px 0 0 0;
padding: 10px 12px;
border: 1px solid #ff6666;
border-radius: 10px;
background-color: rgba(255, 102, 102, 0.12);
color: #ff6666;
font-size: 0.9em;
text-align: center;
box-shadow: 0 4px 12px rgba(255, 102, 102, 0.15);
}
.duration-container .button-row {
display: flex;
flex-direction: row;