mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
refactor(devices): close all MED/LOW review items
- Extract DEVICE_ALIAS_MAX_LENGTH to src/constants/devices.ts so the
user page and admin page share one source of truth (mirroring the
bot's ALIAS_MAX_LENGTH=64). Was duplicated as a magic 64 in both
files (MED-5).
- Replace ✓ / ✕ / ✏️ glyphs in AdminUserDetail.tsx with inline SVGs,
matching the icon style already used by Subscription.tsx for the
same rename/save/cancel actions. Eliminates the platform-dependent
emoji rendering inconsistency (MED-4).
- Add haptic feedback to the user-side renameDeviceMutation —
notification('success') on save, notification('error') on failure.
Brings it in line with the other Subscription.tsx mutations
(LOW-9).
- Translate the new rename keys into zh.json and fa.json (en.json
already covered in the previous follow-up). Cabinet now ships full
parity across all 4 locales for the rename UI.
This commit is contained in:
8
src/constants/devices.ts
Normal file
8
src/constants/devices.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Maximum length of a user-set local device alias.
|
||||
*
|
||||
* Mirrors `ALIAS_MAX_LENGTH` in the bot's `user_device_aliases` table.
|
||||
* Keep this value in sync with `app/database/crud/user_device_alias.py`
|
||||
* (Python side enforces it at the DB column width AND at the API layer).
|
||||
*/
|
||||
export const DEVICE_ALIAS_MAX_LENGTH = 64;
|
||||
@@ -454,6 +454,11 @@
|
||||
"month": "ماه",
|
||||
"myDevices": "دستگاههای من",
|
||||
"noDevices": "هیچ دستگاه متصلی نیست",
|
||||
"renameDevice": "تغییر نام",
|
||||
"renameDeviceSave": "ذخیره",
|
||||
"renameDeviceCancel": "لغو",
|
||||
"renameDevicePlaceholder": "نام دستگاه",
|
||||
"deviceRenamed": "نام دستگاه بهروزرسانی شد",
|
||||
"perExtraDevice": "/ دستگاه اضافی",
|
||||
"perMonth": "/ماه",
|
||||
"purchasedTraffic": "ترافیک خریداری شده",
|
||||
@@ -2667,7 +2672,10 @@
|
||||
"none": "دستگاه متصلی وجود ندارد",
|
||||
"resetAll": "بازنشانی همه",
|
||||
"deleted": "دستگاه حذف شد",
|
||||
"allDeleted": "همه دستگاهها بازنشانی شدند"
|
||||
"allDeleted": "همه دستگاهها بازنشانی شدند",
|
||||
"rename": "تغییر نام",
|
||||
"renameSave": "ذخیره",
|
||||
"renamed": "نام دستگاه بهروزرسانی شد"
|
||||
},
|
||||
"referral": {
|
||||
"title": "برنامه ارجاع",
|
||||
|
||||
@@ -454,6 +454,11 @@
|
||||
"month": "月",
|
||||
"myDevices": "我的设备",
|
||||
"noDevices": "没有已连接的设备",
|
||||
"renameDevice": "重命名",
|
||||
"renameDeviceSave": "保存",
|
||||
"renameDeviceCancel": "取消",
|
||||
"renameDevicePlaceholder": "设备名称",
|
||||
"deviceRenamed": "设备名称已更新",
|
||||
"perExtraDevice": "/ 额外设备",
|
||||
"perMonth": "/月",
|
||||
"purchasedTraffic": "已购流量",
|
||||
@@ -2666,7 +2671,10 @@
|
||||
"none": "没有已连接的设备",
|
||||
"resetAll": "重置全部",
|
||||
"deleted": "设备已删除",
|
||||
"allDeleted": "所有设备已重置"
|
||||
"allDeleted": "所有设备已重置",
|
||||
"rename": "重命名",
|
||||
"renameSave": "保存",
|
||||
"renamed": "设备名称已更新"
|
||||
},
|
||||
"referral": {
|
||||
"title": "推荐计划",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import i18n from '../i18n';
|
||||
import { DEVICE_ALIAS_MAX_LENGTH } from '../constants/devices';
|
||||
import { useCurrency } from '../hooks/useCurrency';
|
||||
import { useNotify } from '../platform/hooks/useNotify';
|
||||
import {
|
||||
@@ -2479,7 +2480,7 @@ export default function AdminUserDetail() {
|
||||
type="text"
|
||||
autoFocus
|
||||
value={editingDeviceName}
|
||||
maxLength={64}
|
||||
maxLength={DEVICE_ALIAS_MAX_LENGTH}
|
||||
placeholder={
|
||||
device.platform ||
|
||||
device.device_model ||
|
||||
@@ -2522,7 +2523,7 @@ export default function AdminUserDetail() {
|
||||
type="button"
|
||||
onClick={() => handleRenameDevice(device.hwid)}
|
||||
disabled={renameSaving}
|
||||
className="rounded-lg px-2 py-1 text-xs text-success-400 transition-all hover:bg-success-500/15 disabled:opacity-50"
|
||||
className="rounded-lg px-2 py-1 text-success-400 transition-all hover:bg-success-500/15 disabled:opacity-50"
|
||||
title={t(
|
||||
'admin.users.detail.devices.renameSave',
|
||||
t(
|
||||
@@ -2530,8 +2531,24 @@ export default function AdminUserDetail() {
|
||||
'\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C',
|
||||
),
|
||||
)}
|
||||
aria-label={t(
|
||||
'admin.users.detail.devices.renameSave',
|
||||
'\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C',
|
||||
)}
|
||||
>
|
||||
{'\u2713'}
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -2540,13 +2557,29 @@ export default function AdminUserDetail() {
|
||||
setEditingDeviceName('');
|
||||
}}
|
||||
disabled={renameSaving}
|
||||
className="rounded-lg px-2 py-1 text-xs text-dark-500 transition-all hover:bg-dark-700 disabled:opacity-50"
|
||||
className="rounded-lg px-2 py-1 text-dark-500 transition-all hover:bg-dark-700 disabled:opacity-50"
|
||||
title={t(
|
||||
'common.cancel',
|
||||
'\u041E\u0442\u043C\u0435\u043D\u0430',
|
||||
)}
|
||||
aria-label={t(
|
||||
'common.cancel',
|
||||
'\u041E\u0442\u043C\u0435\u043D\u0430',
|
||||
)}
|
||||
>
|
||||
{'\u2715'}
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
@@ -2557,13 +2590,29 @@ export default function AdminUserDetail() {
|
||||
setEditingDeviceHwid(device.hwid);
|
||||
setEditingDeviceName(device.local_name || '');
|
||||
}}
|
||||
className="rounded-lg px-2 py-1 text-xs text-dark-500 transition-all hover:bg-accent-500/15 hover:text-accent-400"
|
||||
className="rounded-lg px-2 py-1 text-dark-500 transition-all hover:bg-accent-500/15 hover:text-accent-400"
|
||||
title={t(
|
||||
'admin.users.detail.devices.rename',
|
||||
'\u041F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C',
|
||||
)}
|
||||
aria-label={t(
|
||||
'admin.users.detail.devices.rename',
|
||||
'\u041F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u0442\u044C',
|
||||
)}
|
||||
>
|
||||
{'\u270F\uFE0F'}
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Navigate, useNavigate, useParams } from 'react-router';
|
||||
import { subscriptionApi } from '../api/subscription';
|
||||
import { DEVICE_ALIAS_MAX_LENGTH } from '../constants/devices';
|
||||
import { WebBackButton } from '../components/WebBackButton';
|
||||
import { useDestructiveConfirm } from '../platform/hooks/useNativeDialog';
|
||||
import { usePlatform } from '../platform';
|
||||
@@ -313,19 +314,23 @@ export default function Subscription() {
|
||||
// identifier of the row being edited.
|
||||
const [editingDeviceHwid, setEditingDeviceHwid] = useState<string | null>(null);
|
||||
const [editingDeviceName, setEditingDeviceName] = useState('');
|
||||
const DEVICE_ALIAS_MAX_LENGTH = 64;
|
||||
|
||||
const renameDeviceMutation = useMutation({
|
||||
mutationFn: ({ hwid, name }: { hwid: string; name: string | null }) =>
|
||||
subscriptionApi.renameDevice(hwid, name, subscriptionId),
|
||||
onSuccess: (_data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['devices', subscriptionId] });
|
||||
// Soft success-tap, like other mutations on this page.
|
||||
haptic.notification('success');
|
||||
// Не сбрасываем edit-state, если пользователь уже перешёл на другой
|
||||
// девайс пока шёл запрос — иначе теряем его новый input. Имя не чистим
|
||||
// безусловно: оно либо принадлежит уже другому девайсу (нужно сохранить),
|
||||
// либо инпут уже закрылся (значение не отображается).
|
||||
setEditingDeviceHwid((current) => (current === variables.hwid ? null : current));
|
||||
},
|
||||
onError: () => {
|
||||
haptic.notification('error');
|
||||
},
|
||||
});
|
||||
|
||||
// Pause subscription mutation
|
||||
|
||||
Reference in New Issue
Block a user