From d1e5ce873b3dd1c3ede0c82c73f47f91346c0b04 Mon Sep 17 00:00:00 2001 From: Fringg Date: Sat, 16 May 2026 03:53:49 +0300 Subject: [PATCH] fix(devices): unicode escape bug + onSuccess race + en locale + apiErr surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code-review follow-up on 321c65b. - prettier ate the raw glyphs (✓/✕/✏️) in AdminUserDetail.tsx and emitted \u2713 / \u2715 / \u270F\uFE0F as JSX text children. JSX does NOT process \u… escapes inside element children, so admin users saw the literal six-character strings on Save/Cancel/Rename buttons. Wrap each glyph in a JS expression {'…'} so it survives both prettier and JSX. - renameDeviceMutation.onSuccess on Subscription.tsx no longer unconditionally clears the edit state. If the user already navigated to a different device while the previous save was in flight, we keep their in-progress input. Same shape applied to AdminUserDetail.tsx — the alias is snapshot before the await so a race can't smuggle the wrong value into the request, and the post-success reset is guarded. - AdminUserDetail rename error path now surfaces the backend's response.data.detail (e.g. '64-char limit' message) instead of the generic localized userActions.error toast. - Add the new rename i18n keys to src/locales/en.json so English users no longer fall back to Russian labels. ua/zh/fa to follow in a dedicated translation pass. --- src/locales/en.json | 10 +++++++++- src/pages/AdminUserDetail.tsx | 22 +++++++++++++--------- src/pages/Subscription.tsx | 9 ++++++--- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index dbb137c..f50417f 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -468,6 +468,11 @@ "confirmDeleteAllDevices": "Delete all devices?", "deviceDeleted": "Device deleted", "allDevicesDeleted": "All devices deleted", + "renameDevice": "Rename", + "renameDeviceSave": "Save", + "renameDeviceCancel": "Cancel", + "renameDevicePlaceholder": "Device name", + "deviceRenamed": "Device name updated", "platform": "Platform", "model": "Model", "connectedAt": "Connected", @@ -3176,7 +3181,10 @@ "none": "No connected devices", "resetAll": "Reset all", "deleted": "Device removed", - "allDeleted": "All devices reset" + "allDeleted": "All devices reset", + "rename": "Rename", + "renameSave": "Save", + "renamed": "Device name updated" }, "referral": { "title": "Referral program", diff --git a/src/pages/AdminUserDetail.tsx b/src/pages/AdminUserDetail.tsx index 59d8e33..19fd343 100644 --- a/src/pages/AdminUserDetail.tsx +++ b/src/pages/AdminUserDetail.tsx @@ -774,15 +774,19 @@ export default function AdminUserDetail() { const handleRenameDevice = async (hwid: string) => { if (!userId) return; setRenameSaving(true); + // Snapshot inputs BEFORE the await so a fast click on another device + // mid-flight doesn't smuggle a different alias into this hwid's request. + const snapshotName = editingDeviceName.trim(); try { - const trimmed = editingDeviceName.trim(); - await adminUsersApi.renameUserDevice(userId, hwid, trimmed || null); + await adminUsersApi.renameUserDevice(userId, hwid, snapshotName || null); notify.success(t('admin.users.detail.devices.renamed', 'Имя устройства обновлено')); - setEditingDeviceHwid(null); - setEditingDeviceName(''); + // Reset edit state only if user is still on the saved row. + setEditingDeviceHwid((current) => (current === hwid ? null : current)); await loadDevices(); - } catch { - notify.error(t('admin.users.userActions.error'), t('common.error')); + } catch (err) { + const apiMessage = (err as { response?: { data?: { detail?: string } } })?.response?.data + ?.detail; + notify.error(apiMessage || t('admin.users.userActions.error'), t('common.error')); } finally { setRenameSaving(false); } @@ -2527,7 +2531,7 @@ export default function AdminUserDetail() { ), )} > - \u2713 + {'\u2713'} ) : ( @@ -2559,7 +2563,7 @@ export default function AdminUserDetail() { '\u041F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C', )} > - \u270F\uFE0F + {'\u270F\uFE0F'}