feat: persist support ticket drafts

This commit is contained in:
3252a8
2026-05-27 08:10:08 +03:00
parent 69d3400310
commit e90988ea5c
6 changed files with 183 additions and 2 deletions
@@ -14,6 +14,12 @@
if (disabled || sending || !value.trim()) return;
onSend(value.trim());
}
function handleKeydown(event) {
if (!(event.ctrlKey || event.metaKey) || event.key !== "Enter") return;
event.preventDefault();
submit();
}
</script>
<div class="ticket-composer">
@@ -25,6 +31,7 @@
{placeholder}
ariaLabel={placeholder}
class="ticket-composer-textarea"
on:keydown={handleKeydown}
/>
<div class="ticket-composer-row">
<small>{value.length}/{maxLength}</small>
@@ -250,7 +250,7 @@ export function createSupportStore({ api, t, showToast }) {
});
if (!ticketId) {
state.update((s) => ({ ...s, sending: false }));
return;
return false;
}
try {
const res = await api(`/support/tickets/${ticketId}/messages`, {
@@ -265,8 +265,10 @@ export function createSupportStore({ api, t, showToast }) {
}));
await refreshUnread();
await loadList({ silent: true, force: true });
return true;
} catch (error) {
showToast(error?.message || t("wa_support_send_failed"));
return false;
} finally {
state.update((s) => ({ ...s, sending: false }));
}
+83
View File
@@ -0,0 +1,83 @@
const SUPPORT_DRAFT_STORAGE_PREFIX = "rw_webapp_support_draft_v1";
const SUPPORT_DRAFT_TTL_MS = 14 * 24 * 60 * 60 * 1000;
const DEFAULT_SCOPE = "anonymous";
function safeStorage() {
if (typeof window === "undefined") return null;
try {
return window.localStorage || null;
} catch (_error) {
return null;
}
}
function draftKey(kind, scope, id = "new") {
return [
SUPPORT_DRAFT_STORAGE_PREFIX,
encodeURIComponent(String(kind || "draft")),
encodeURIComponent(String(scope || DEFAULT_SCOPE)),
encodeURIComponent(String(id || "new")),
].join(":");
}
function normalizeDraftEnvelope(value) {
if (!value || typeof value !== "object") return null;
const updatedAt = Number(value.updatedAt || 0);
if (!updatedAt || Date.now() - updatedAt > SUPPORT_DRAFT_TTL_MS) return null;
return value.draft && typeof value.draft === "object" ? value.draft : null;
}
export function supportDraftScope(user = {}) {
const id = String(user?.user_id ?? user?.id ?? user?.telegram_id ?? "").trim();
return id || DEFAULT_SCOPE;
}
export function readSupportDraft(kind, scope, id = "new") {
const storage = safeStorage();
if (!storage) return null;
const key = draftKey(kind, scope, id);
try {
const raw = storage.getItem(key);
if (!raw) return null;
const draft = normalizeDraftEnvelope(JSON.parse(raw));
if (!draft) storage.removeItem(key);
return draft;
} catch (_error) {
try {
storage.removeItem(key);
} catch (_removeError) {
void _removeError;
}
return null;
}
}
export function writeSupportDraft(kind, scope, id = "new", draft = {}) {
const storage = safeStorage();
if (!storage) return;
try {
storage.setItem(
draftKey(kind, scope, id),
JSON.stringify({
updatedAt: Date.now(),
draft: draft && typeof draft === "object" ? draft : {},
})
);
} catch (_error) {
void _error;
}
}
export function clearSupportDraft(kind, scope, id = "new") {
const storage = safeStorage();
if (!storage) return;
try {
storage.removeItem(draftKey(kind, scope, id));
} catch (_error) {
void _error;
}
}