mirror of
https://github.com/Qortal/names.git
synced 2025-06-15 02:31:22 +00:00
fixes
This commit is contained in:
parent
e76a417f31
commit
01bc736d6b
@ -8,8 +8,8 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
Button,
|
Button,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useAtom, useSetAtom } from 'jotai';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef, useMemo } from 'react';
|
||||||
import { TableVirtuoso, TableComponents } from 'react-virtuoso';
|
import { TableVirtuoso, TableComponents } from 'react-virtuoso';
|
||||||
import {
|
import {
|
||||||
forSaleAtom,
|
forSaleAtom,
|
||||||
@ -105,7 +105,9 @@ function rowContent(
|
|||||||
setNames: SetNames,
|
setNames: SetNames,
|
||||||
setNamesForSale: SetNamesForSale,
|
setNamesForSale: SetNamesForSale,
|
||||||
isPrimaryNameForSale: boolean,
|
isPrimaryNameForSale: boolean,
|
||||||
t: TFunction
|
t: TFunction,
|
||||||
|
nameStrings: string[],
|
||||||
|
pendingBuyNameStrings: string[]
|
||||||
) {
|
) {
|
||||||
const handleBuy = async (name: string) => {
|
const handleBuy = async (name: string) => {
|
||||||
const loadId = showLoading(
|
const loadId = showLoading(
|
||||||
@ -163,13 +165,17 @@ function rowContent(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isNameOwned = nameStrings.includes(row.name);
|
||||||
|
|
||||||
|
const isNameBuying = pendingBuyNameStrings.includes(row.name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TableCell>{row.name}</TableCell>
|
<TableCell>{row.name}</TableCell>
|
||||||
<TableCell>{row.salePrice}</TableCell>
|
<TableCell>{row.salePrice}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Button
|
<Button
|
||||||
disabled={isPrimaryNameForSale}
|
disabled={isPrimaryNameForSale || isNameOwned || isNameBuying}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleBuy(row.name)}
|
onClick={() => handleBuy(row.name)}
|
||||||
@ -198,10 +204,20 @@ export const ForSaleTable = ({
|
|||||||
handleSort,
|
handleSort,
|
||||||
isPrimaryNameForSale,
|
isPrimaryNameForSale,
|
||||||
}: ForSaleTable) => {
|
}: ForSaleTable) => {
|
||||||
const setNames = useSetAtom(namesAtom);
|
const [names, setNames] = useAtom(namesAtom);
|
||||||
const setNamesForSale = useSetAtom(forSaleAtom);
|
const setNamesForSale = useSetAtom(forSaleAtom);
|
||||||
const setPendingTxs = useSetAtom(pendingTxsAtom);
|
const [pendingTxs, setPendingTxs] = useAtom(pendingTxsAtom);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const nameStrings = useMemo(() => {
|
||||||
|
return names?.map((item) => item.name);
|
||||||
|
}, [names]);
|
||||||
|
const pendingBuyNameStrings = useMemo(() => {
|
||||||
|
const buyNameTxs = pendingTxs['BUY_NAME'];
|
||||||
|
if (!buyNameTxs) return [];
|
||||||
|
|
||||||
|
return Object.values(buyNameTxs).map((tx) => tx.name);
|
||||||
|
}, [pendingTxs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
sx={{
|
sx={{
|
||||||
@ -223,7 +239,9 @@ export const ForSaleTable = ({
|
|||||||
setNames,
|
setNames,
|
||||||
setNamesForSale,
|
setNamesForSale,
|
||||||
isPrimaryNameForSale,
|
isPrimaryNameForSale,
|
||||||
t
|
t,
|
||||||
|
nameStrings,
|
||||||
|
pendingBuyNameStrings
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -63,6 +63,7 @@ import { TFunction } from 'i18next';
|
|||||||
interface NameData {
|
interface NameData {
|
||||||
name: string;
|
name: string;
|
||||||
isSelling?: boolean;
|
isSelling?: boolean;
|
||||||
|
forceUpdateState?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNameQueue = new RequestQueueWithPromise(2);
|
const getNameQueue = new RequestQueueWithPromise(2);
|
||||||
@ -212,15 +213,17 @@ function rowContent(
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const loadId = showLoading(
|
let loadId = null;
|
||||||
t('core:update_name.responses.loading', {
|
|
||||||
postProcess: 'capitalizeFirstChar',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await modalFunctionsUpdateName.show(undefined);
|
const response = await modalFunctionsUpdateName.show(undefined);
|
||||||
|
loadId = showLoading(
|
||||||
|
t('core:update_name.responses.loading', {
|
||||||
|
postProcess: 'capitalizeFirstChar',
|
||||||
|
})
|
||||||
|
);
|
||||||
if (typeof response !== 'string') throw new Error('Invalid name');
|
if (typeof response !== 'string') throw new Error('Invalid name');
|
||||||
|
|
||||||
const res = await qortalRequest({
|
const res = await qortalRequest({
|
||||||
action: 'UPDATE_NAME',
|
action: 'UPDATE_NAME',
|
||||||
newName: response,
|
newName: response,
|
||||||
@ -264,13 +267,15 @@ function rowContent(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showError(
|
showError(
|
||||||
t('core:update_name.responses.loading', {
|
t('core:update_name.responses.error', {
|
||||||
postProcess: 'capitalizeFirstChar',
|
postProcess: 'capitalizeFirstChar',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
console.log('error', error);
|
console.log('error', error);
|
||||||
} finally {
|
} finally {
|
||||||
dismissToast(loadId);
|
if (loadId) {
|
||||||
|
dismissToast(loadId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Your logic here
|
// Your logic here
|
||||||
@ -285,16 +290,17 @@ function rowContent(
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const loadId = showLoading(
|
let loadId = null;
|
||||||
t('core:sell_name.responses.loading', {
|
|
||||||
postProcess: 'capitalizeFirstChar',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
try {
|
try {
|
||||||
if (name === primaryName) {
|
if (name === primaryName) {
|
||||||
await modalFunctions.show({ name });
|
await modalFunctions.show({ name });
|
||||||
}
|
}
|
||||||
const price = await modalFunctionsSellName.show(name);
|
const price = await modalFunctionsSellName.show(name);
|
||||||
|
loadId = showLoading(
|
||||||
|
t('core:sell_name.responses.loading', {
|
||||||
|
postProcess: 'capitalizeFirstChar',
|
||||||
|
})
|
||||||
|
);
|
||||||
if (typeof price !== 'string' && typeof price !== 'number')
|
if (typeof price !== 'string' && typeof price !== 'number')
|
||||||
throw new Error(
|
throw new Error(
|
||||||
t('core:sell_name.responses.error3', {
|
t('core:sell_name.responses.error3', {
|
||||||
@ -347,13 +353,15 @@ function rowContent(
|
|||||||
);
|
);
|
||||||
console.log('error', error);
|
console.log('error', error);
|
||||||
} finally {
|
} finally {
|
||||||
dismissToast(loadId);
|
if (loadId) {
|
||||||
|
dismissToast(loadId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = async (name: string) => {
|
const handleCancel = async (name: string) => {
|
||||||
const loadId = showLoading(
|
const loadId = showLoading(
|
||||||
t('core:cancel_name.responses.error2', {
|
t('core:cancel_name.responses.loading', {
|
||||||
postProcess: 'capitalizeFirstChar',
|
postProcess: 'capitalizeFirstChar',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -381,7 +389,7 @@ function rowContent(
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
showSuccess(
|
showSuccess(
|
||||||
t('core:cancel_name.responses.error2', {
|
t('core:cancel_name.responses.success', {
|
||||||
postProcess: 'capitalizeFirstChar',
|
postProcess: 'capitalizeFirstChar',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -391,12 +399,14 @@ function rowContent(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showError(
|
showError(
|
||||||
t('core:cancel_name.responses.error2', {
|
t('core:cancel_name.responses.error', {
|
||||||
postProcess: 'capitalizeFirstChar',
|
postProcess: 'capitalizeFirstChar',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
dismissToast(loadId);
|
if (loadId) {
|
||||||
|
dismissToast(loadId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -408,8 +418,20 @@ function rowContent(
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '5px',
|
gap: '5px',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
wordBreak: 'break-word',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
height: '30px',
|
||||||
|
width: '30px',
|
||||||
|
objectFit: 'contain',
|
||||||
|
}}
|
||||||
|
src={`/arbitrary/THUMBNAIL/${row.name}/qortal_avatar?forceUpdateState=${row?.forceUpdateState}`}
|
||||||
|
alt={row.name}
|
||||||
|
>
|
||||||
|
{row.name?.charAt(0)}
|
||||||
|
</Avatar>
|
||||||
{primaryName === row.name && (
|
{primaryName === row.name && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={t('core:tooltips.primary_name', {
|
title={t('core:tooltips.primary_name', {
|
||||||
@ -467,6 +489,7 @@ function rowContent(
|
|||||||
color="error"
|
color="error"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleCancel(row.name)}
|
onClick={() => handleCancel(row.name)}
|
||||||
|
variant="contained"
|
||||||
disabled={isNameCurrentlyDoingATx}
|
disabled={isNameCurrentlyDoingATx}
|
||||||
>
|
>
|
||||||
{t('core:actions.cancel_sell', {
|
{t('core:actions.cancel_sell', {
|
||||||
@ -495,6 +518,7 @@ export const NameTable = ({ names, primaryName }: NameTableProps) => {
|
|||||||
const [namesForSale, setNamesForSale] = useAtom(forSaleAtom);
|
const [namesForSale, setNamesForSale] = useAtom(forSaleAtom);
|
||||||
const [pendingTxs] = useAtom(pendingTxsAtom);
|
const [pendingTxs] = useAtom(pendingTxsAtom);
|
||||||
const { t } = useTranslation(['core']);
|
const { t } = useTranslation(['core']);
|
||||||
|
const [forceUpdateState, forceUpdate] = useState(0);
|
||||||
|
|
||||||
const modalFunctions = useModal<{ name: string }>();
|
const modalFunctions = useModal<{ name: string }>();
|
||||||
const modalFunctionsUpdateName = useModal();
|
const modalFunctionsUpdateName = useModal();
|
||||||
@ -504,15 +528,20 @@ export const NameTable = ({ names, primaryName }: NameTableProps) => {
|
|||||||
|
|
||||||
const setPendingTxs = useSetAtom(pendingTxsAtom);
|
const setPendingTxs = useSetAtom(pendingTxsAtom);
|
||||||
|
|
||||||
|
const triggerRerender = useCallback(() => {
|
||||||
|
forceUpdate((n) => n + 1);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const namesToDisplay = useMemo(() => {
|
const namesToDisplay = useMemo(() => {
|
||||||
const namesForSaleString = namesForSale.map((item) => item.name);
|
const namesForSaleString = namesForSale.map((item) => item.name);
|
||||||
return names.map((name) => {
|
return names.map((name) => {
|
||||||
return {
|
return {
|
||||||
name: name.name,
|
name: name.name,
|
||||||
isSelling: namesForSaleString.includes(name.name),
|
isSelling: namesForSaleString.includes(name.name),
|
||||||
|
forceUpdateState,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, [names, namesForSale]);
|
}, [names, namesForSale, forceUpdateState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
@ -596,7 +625,11 @@ export const NameTable = ({ names, primaryName }: NameTableProps) => {
|
|||||||
<UpdateNameModal modalFunctionsUpdateName={modalFunctionsUpdateName} />
|
<UpdateNameModal modalFunctionsUpdateName={modalFunctionsUpdateName} />
|
||||||
)}
|
)}
|
||||||
{modalFunctionsAvatar?.isShow && (
|
{modalFunctionsAvatar?.isShow && (
|
||||||
<AvatarModal modalFunctionsAvatar={modalFunctionsAvatar} />
|
<AvatarModal
|
||||||
|
modalFunctionsAvatar={modalFunctionsAvatar}
|
||||||
|
triggerRerender={triggerRerender}
|
||||||
|
forceUpdateState={forceUpdateState}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{modalFunctionsSellName?.isShow && (
|
{modalFunctionsSellName?.isShow && (
|
||||||
<SellNameModal modalFunctionsSellName={modalFunctionsSellName} />
|
<SellNameModal modalFunctionsSellName={modalFunctionsSellName} />
|
||||||
@ -612,8 +645,14 @@ interface PickedAvatar {
|
|||||||
|
|
||||||
interface AvatarModalProps {
|
interface AvatarModalProps {
|
||||||
modalFunctionsAvatar: ModalFunctionsAvatar;
|
modalFunctionsAvatar: ModalFunctionsAvatar;
|
||||||
|
triggerRerender: () => void;
|
||||||
|
forceUpdateState?: number;
|
||||||
}
|
}
|
||||||
const AvatarModal = ({ modalFunctionsAvatar }: AvatarModalProps) => {
|
const AvatarModal = ({
|
||||||
|
modalFunctionsAvatar,
|
||||||
|
triggerRerender,
|
||||||
|
forceUpdateState,
|
||||||
|
}: AvatarModalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setHasAvatar } = usePendingTxs();
|
const { setHasAvatar } = usePendingTxs();
|
||||||
const forceRefresh = useSetAtom(forceRefreshAtom);
|
const forceRefresh = useSetAtom(forceRefreshAtom);
|
||||||
@ -651,6 +690,7 @@ const AvatarModal = ({ modalFunctionsAvatar }: AvatarModalProps) => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
modalFunctionsAvatar.onOk(undefined);
|
modalFunctionsAvatar.onOk(undefined);
|
||||||
|
triggerRerender();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
showError(error?.message);
|
showError(error?.message);
|
||||||
@ -699,10 +739,10 @@ const AvatarModal = ({ modalFunctionsAvatar }: AvatarModalProps) => {
|
|||||||
height: '138px',
|
height: '138px',
|
||||||
width: '138px',
|
width: '138px',
|
||||||
}}
|
}}
|
||||||
src={`/arbitrary/THUMBNAIL/${modalFunctionsAvatar.data.name}/qortal_avatar?async=true`}
|
src={`/arbitrary/THUMBNAIL/${modalFunctionsAvatar.data.name}/qortal_avatar?forceUpdateState=${forceUpdateState}`}
|
||||||
alt={modalFunctionsAvatar.data.name}
|
alt={modalFunctionsAvatar.data.name}
|
||||||
>
|
>
|
||||||
<CircularProgress />
|
{modalFunctionsAvatar?.data?.name?.charAt(0)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
)}
|
)}
|
||||||
{pickedAvatar?.base64 && (
|
{pickedAvatar?.base64 && (
|
||||||
@ -1021,7 +1061,7 @@ interface SellNameModalProps {
|
|||||||
|
|
||||||
const SellNameModal = ({ modalFunctionsSellName }: SellNameModalProps) => {
|
const SellNameModal = ({ modalFunctionsSellName }: SellNameModalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [price, setPrice] = useState(0);
|
const [price, setPrice] = useState<number | string>(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -1044,10 +1084,24 @@ const SellNameModal = ({ modalFunctionsSellName }: SellNameModalProps) => {
|
|||||||
<TextField
|
<TextField
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
autoFocus
|
autoFocus
|
||||||
onChange={(e) => setPrice(+e.target.value)}
|
onChange={(e) => {
|
||||||
|
const raw = e.target.value;
|
||||||
|
|
||||||
|
// Allow empty input
|
||||||
|
if (raw === '') {
|
||||||
|
setPrice('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading zeros and convert to number
|
||||||
|
const numericValue = +raw;
|
||||||
|
if (!isNaN(numericValue)) {
|
||||||
|
setPrice(numericValue);
|
||||||
|
}
|
||||||
|
}}
|
||||||
value={price}
|
value={price}
|
||||||
type="number"
|
type="number"
|
||||||
placeholder={t('core:new_name.choose_price', {
|
placeholder={t('core:sell_name.choose_price', {
|
||||||
postProcess: 'capitalizeFirstChar',
|
postProcess: 'capitalizeFirstChar',
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
96
src/i18n/locales/zh/core.json
Normal file
96
src/i18n/locales/zh/core.json
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"my_names": "我的名称",
|
||||||
|
"market": "待售名称"
|
||||||
|
},
|
||||||
|
"inputs": {
|
||||||
|
"filter_names": "筛选名称"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"new_name": "新名称",
|
||||||
|
"register_name": "注册名称",
|
||||||
|
"close": "关闭",
|
||||||
|
"update_avatar": "更新头像",
|
||||||
|
"set_avatar": "设置头像",
|
||||||
|
"update": "更新",
|
||||||
|
"sell": "出售",
|
||||||
|
"cancel_sell": "取消出售",
|
||||||
|
"cancel": "取消",
|
||||||
|
"continue": "继续",
|
||||||
|
"publish": "发布",
|
||||||
|
"buy": "购买",
|
||||||
|
"choose_image": "选择图片"
|
||||||
|
},
|
||||||
|
"new_name": {
|
||||||
|
"choose_name": "选择一个名称",
|
||||||
|
"balance_message": "你的余额为 {{balance}} QORT。注册名称需要支付 {{nameFee}} QORT。",
|
||||||
|
"name_available": "{{name}} 可用",
|
||||||
|
"name_unavailable": "{{name}} 不可用",
|
||||||
|
"checking_name": "正在检查名称是否已存在",
|
||||||
|
"responses": {
|
||||||
|
"success": "名称注册成功",
|
||||||
|
"error": "无法注册名称",
|
||||||
|
"loading": "正在注册名称...请稍候"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tables": {
|
||||||
|
"name": "名称",
|
||||||
|
"actions": "操作"
|
||||||
|
},
|
||||||
|
"update_name": {
|
||||||
|
"responses": {
|
||||||
|
"loading": "正在更新名称...请稍候",
|
||||||
|
"success": "名称更新成功",
|
||||||
|
"error": "无法更新名称"
|
||||||
|
},
|
||||||
|
"title": "更新名称时请注意",
|
||||||
|
"choose_name": "选择新名称",
|
||||||
|
"balance_info": "你的余额为 {{balance}} QORT。注册名称需要支付 {{nameFee}} QORT。",
|
||||||
|
"name_available": "{{name}} 可用",
|
||||||
|
"name_unavailable": "{{name}} 不可用"
|
||||||
|
},
|
||||||
|
"sell_name": {
|
||||||
|
"responses": {
|
||||||
|
"loading": "正在上架名称出售...请稍候",
|
||||||
|
"success": "名称已上架出售",
|
||||||
|
"error1": "拥有其他名称时无法出售主名称",
|
||||||
|
"error2": "无法上架名称出售",
|
||||||
|
"error3": "无效的价格"
|
||||||
|
},
|
||||||
|
"title": "出售名称",
|
||||||
|
"choose_price": "设定出售价格"
|
||||||
|
},
|
||||||
|
"cancel_name": {
|
||||||
|
"responses": {
|
||||||
|
"loading": "正在从市场移除名称...请稍候",
|
||||||
|
"success": "名称已从市场移除",
|
||||||
|
"error": "无法从市场移除名称"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tooltips": {
|
||||||
|
"primary_name": "这是你的主名称(身份)"
|
||||||
|
},
|
||||||
|
"warnings": {
|
||||||
|
"warning": "警告",
|
||||||
|
"primary_name_sell_caution": "出售主名称时请谨慎",
|
||||||
|
"primary_name_sell": "{{name}} 是你的主名称。如果你是某个私密群组的管理员,出售该名称将移除你在该群组的密钥。请确保其他管理员在出售前重新加密最新密钥。请谨慎操作!",
|
||||||
|
"update_name1": "如果你更新了名称,你将失去与原名称关联的资源。换句话说,你将失去 QDN 上该名称下内容的所有权。请谨慎操作!"
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"responses": {
|
||||||
|
"loading": "正在发布头像...请稍候",
|
||||||
|
"success": "头像发布成功",
|
||||||
|
"error1": "缺少数据",
|
||||||
|
"error2": "无法发布头像"
|
||||||
|
},
|
||||||
|
"title": "发布头像"
|
||||||
|
},
|
||||||
|
"market": {
|
||||||
|
"sale_price": "售价",
|
||||||
|
"responses": {
|
||||||
|
"loading": "正在尝试购买名称...请稍候",
|
||||||
|
"success": "名称购买成功",
|
||||||
|
"error": "无法购买名称"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user