mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
fix(devices): unicode escape bug + onSuccess race + en locale + apiErr surface
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.
This commit is contained in:
@@ -468,6 +468,11 @@
|
|||||||
"confirmDeleteAllDevices": "Delete all devices?",
|
"confirmDeleteAllDevices": "Delete all devices?",
|
||||||
"deviceDeleted": "Device deleted",
|
"deviceDeleted": "Device deleted",
|
||||||
"allDevicesDeleted": "All devices deleted",
|
"allDevicesDeleted": "All devices deleted",
|
||||||
|
"renameDevice": "Rename",
|
||||||
|
"renameDeviceSave": "Save",
|
||||||
|
"renameDeviceCancel": "Cancel",
|
||||||
|
"renameDevicePlaceholder": "Device name",
|
||||||
|
"deviceRenamed": "Device name updated",
|
||||||
"platform": "Platform",
|
"platform": "Platform",
|
||||||
"model": "Model",
|
"model": "Model",
|
||||||
"connectedAt": "Connected",
|
"connectedAt": "Connected",
|
||||||
@@ -3176,7 +3181,10 @@
|
|||||||
"none": "No connected devices",
|
"none": "No connected devices",
|
||||||
"resetAll": "Reset all",
|
"resetAll": "Reset all",
|
||||||
"deleted": "Device removed",
|
"deleted": "Device removed",
|
||||||
"allDeleted": "All devices reset"
|
"allDeleted": "All devices reset",
|
||||||
|
"rename": "Rename",
|
||||||
|
"renameSave": "Save",
|
||||||
|
"renamed": "Device name updated"
|
||||||
},
|
},
|
||||||
"referral": {
|
"referral": {
|
||||||
"title": "Referral program",
|
"title": "Referral program",
|
||||||
|
|||||||
@@ -774,15 +774,19 @@ export default function AdminUserDetail() {
|
|||||||
const handleRenameDevice = async (hwid: string) => {
|
const handleRenameDevice = async (hwid: string) => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
setRenameSaving(true);
|
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 {
|
try {
|
||||||
const trimmed = editingDeviceName.trim();
|
await adminUsersApi.renameUserDevice(userId, hwid, snapshotName || null);
|
||||||
await adminUsersApi.renameUserDevice(userId, hwid, trimmed || null);
|
|
||||||
notify.success(t('admin.users.detail.devices.renamed', 'Имя устройства обновлено'));
|
notify.success(t('admin.users.detail.devices.renamed', 'Имя устройства обновлено'));
|
||||||
setEditingDeviceHwid(null);
|
// Reset edit state only if user is still on the saved row.
|
||||||
setEditingDeviceName('');
|
setEditingDeviceHwid((current) => (current === hwid ? null : current));
|
||||||
await loadDevices();
|
await loadDevices();
|
||||||
} catch {
|
} catch (err) {
|
||||||
notify.error(t('admin.users.userActions.error'), t('common.error'));
|
const apiMessage = (err as { response?: { data?: { detail?: string } } })?.response?.data
|
||||||
|
?.detail;
|
||||||
|
notify.error(apiMessage || t('admin.users.userActions.error'), t('common.error'));
|
||||||
} finally {
|
} finally {
|
||||||
setRenameSaving(false);
|
setRenameSaving(false);
|
||||||
}
|
}
|
||||||
@@ -2527,7 +2531,7 @@ export default function AdminUserDetail() {
|
|||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
\u2713
|
{'\u2713'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -2542,7 +2546,7 @@ export default function AdminUserDetail() {
|
|||||||
'\u041E\u0442\u043C\u0435\u043D\u0430',
|
'\u041E\u0442\u043C\u0435\u043D\u0430',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
\u2715
|
{'\u2715'}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -2559,7 +2563,7 @@ export default function AdminUserDetail() {
|
|||||||
'\u041F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C',
|
'\u041F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
\u270F\uFE0F
|
{'\u270F\uFE0F'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|||||||
@@ -318,10 +318,13 @@ export default function Subscription() {
|
|||||||
const renameDeviceMutation = useMutation({
|
const renameDeviceMutation = useMutation({
|
||||||
mutationFn: ({ hwid, name }: { hwid: string; name: string | null }) =>
|
mutationFn: ({ hwid, name }: { hwid: string; name: string | null }) =>
|
||||||
subscriptionApi.renameDevice(hwid, name, subscriptionId),
|
subscriptionApi.renameDevice(hwid, name, subscriptionId),
|
||||||
onSuccess: () => {
|
onSuccess: (_data, variables) => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] });
|
queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] });
|
||||||
setEditingDeviceHwid(null);
|
// Не сбрасываем edit-state, если пользователь уже перешёл на другой
|
||||||
setEditingDeviceName('');
|
// девайс пока шёл запрос — иначе теряем его новый input. Имя не чистим
|
||||||
|
// безусловно: оно либо принадлежит уже другому девайсу (нужно сохранить),
|
||||||
|
// либо инпут уже закрылся (значение не отображается).
|
||||||
|
setEditingDeviceHwid((current) => (current === variables.hwid ? null : current));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user