feat: add profile language preferences and refine toast, dropdown and history UI
This commit is contained in:
+21
-11
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { logDebug, logError, logWarn } from "@rustpad/logger";
|
||||
import { formatNumber, setLanguage, translateApiMessage } from "@rustpad/i18n";
|
||||
|
||||
const DEFAULT_ERRORS = {
|
||||
400: "Invalid request.",
|
||||
@@ -58,9 +59,9 @@ async function csrfToken({ refresh = false } = {}) {
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(bytes % (1024 * 1024) ? 1 : 0)} MB`;
|
||||
if (bytes >= 1024) return `${Math.ceil(bytes / 1024)} KB`;
|
||||
return `${bytes} B`;
|
||||
if (bytes >= 1024 * 1024) return `${formatNumber(bytes / (1024 * 1024), { maximumFractionDigits: bytes % (1024 * 1024) ? 1 : 0 })} MB`;
|
||||
if (bytes >= 1024) return `${formatNumber(Math.ceil(bytes / 1024))} KB`;
|
||||
return `${formatNumber(bytes)} B`;
|
||||
}
|
||||
|
||||
function clearExpiredSession() {
|
||||
@@ -69,7 +70,9 @@ function clearExpiredSession() {
|
||||
sessionStorage.removeItem("rustpad:auth-token");
|
||||
localStorage.removeItem("rustpad:nickname");
|
||||
sessionStorage.removeItem("rustpad:nickname");
|
||||
localStorage.removeItem("rustpad:language");
|
||||
document.cookie = "rustpad_nickname=; Path=/; SameSite=Lax; Max-Age=0";
|
||||
void setLanguage("en", { persist: false });
|
||||
window.dispatchEvent(new CustomEvent("rustpad:session-expired"));
|
||||
}
|
||||
|
||||
@@ -91,7 +94,9 @@ function validateUploadSize(body, configuredMaxBytes) {
|
||||
if (!Number.isFinite(maxBytes) || maxBytes <= 0) return;
|
||||
for (const value of body.values()) {
|
||||
if (value instanceof File && value.size > maxBytes) {
|
||||
const error = new Error(`The selected file is ${formatBytes(value.size)}. The upload limit is ${formatBytes(maxBytes)}.`);
|
||||
const sourceMessage = `The selected file is ${formatBytes(value.size)}. The upload limit is ${formatBytes(maxBytes)}.`;
|
||||
const error = new Error(translateApiMessage(sourceMessage));
|
||||
error.serverMessage = sourceMessage;
|
||||
error.status = 413;
|
||||
throw error;
|
||||
}
|
||||
@@ -110,7 +115,9 @@ async function requestHeaders(options, body) {
|
||||
}
|
||||
|
||||
function requestError(status, data = {}) {
|
||||
const error = new Error(data.error || DEFAULT_ERRORS[status] || `Request failed (${status}).`);
|
||||
const serverMessage = data.error || DEFAULT_ERRORS[status] || `Request failed (${status}).`;
|
||||
const error = new Error(translateApiMessage(serverMessage));
|
||||
error.serverMessage = serverMessage;
|
||||
error.status = status;
|
||||
return error;
|
||||
}
|
||||
@@ -153,11 +160,11 @@ export async function api(path, options = {}) {
|
||||
} catch (error) {
|
||||
if (error.name === "AbortError") {
|
||||
logWarn("api.timeout", { method: options.method || "GET", path });
|
||||
throw new Error("Timed out");
|
||||
throw new Error(translateApiMessage("Timed out"));
|
||||
}
|
||||
logError("api.network_error", error, { method: options.method || "GET", path });
|
||||
if (options.body instanceof FormData && error instanceof TypeError) {
|
||||
throw new Error("Upload failed before the server returned a response. The file may exceed the server or proxy upload limit.");
|
||||
throw new Error(translateApiMessage("Upload failed before the server returned a response. The file may exceed the server or proxy upload limit."));
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
@@ -261,15 +268,17 @@ export function uploadWithProgress(path, options = {}) {
|
||||
reject(error);
|
||||
});
|
||||
xhr.addEventListener("error", () => {
|
||||
const error = new Error("Upload failed before the server returned a response. Check the connection and try again.");
|
||||
const error = new Error(translateApiMessage("Upload failed before the server returned a response. Check the connection and try again."));
|
||||
logError("api.network_error", error, { method, path });
|
||||
fail(error);
|
||||
});
|
||||
xhr.addEventListener("abort", () => {
|
||||
const error = new Error(stalled
|
||||
const sourceMessage = stalled
|
||||
? "Upload stopped making progress. Check the connection and try again."
|
||||
: responseTimedOut ? "The file was sent, but the server did not finish processing it. Try again."
|
||||
: externallyAborted ? "Upload cancelled." : "Upload interrupted. Try again.");
|
||||
: externallyAborted ? "Upload cancelled." : "Upload interrupted. Try again.";
|
||||
const error = new Error(translateApiMessage(sourceMessage));
|
||||
error.serverMessage = sourceMessage;
|
||||
error.name = externallyAborted ? "AbortError" : "UploadError";
|
||||
logWarn(stalled ? "api.upload_stalled" : responseTimedOut ? "api.upload_response_timeout" : "api.upload_aborted", { method, path });
|
||||
fail(error);
|
||||
@@ -278,7 +287,8 @@ export function uploadWithProgress(path, options = {}) {
|
||||
if (options.signal) {
|
||||
if (options.signal.aborted) {
|
||||
externallyAborted = true;
|
||||
const error = new Error("Upload cancelled.");
|
||||
const error = new Error(translateApiMessage("Upload cancelled."));
|
||||
error.serverMessage = "Upload cancelled.";
|
||||
error.name = "AbortError";
|
||||
fail(error);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user