mirror of
https://github.com/Qortal/names.git
synced 2025-06-14 18:21:21 +00:00
fixes
This commit is contained in:
parent
e76a417f31
commit
01bc736d6b
@ -8,8 +8,8 @@ import {
|
||||
Paper,
|
||||
Button,
|
||||
} from '@mui/material';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { forwardRef } from 'react';
|
||||
import { useAtom, useSetAtom } from 'jotai';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { TableVirtuoso, TableComponents } from 'react-virtuoso';
|
||||
import {
|
||||
forSaleAtom,
|
||||
@ -105,7 +105,9 @@ function rowContent(
|
||||
setNames: SetNames,
|
||||
setNamesForSale: SetNamesForSale,
|
||||
isPrimaryNameForSale: boolean,
|
||||
t: TFunction
|
||||
t: TFunction,
|
||||
nameStrings: string[],
|
||||
pendingBuyNameStrings: string[]
|
||||
) {
|
||||
const handleBuy = async (name: string) => {
|
||||
const loadId = showLoading(
|
||||
@ -163,13 +165,17 @@ function rowContent(
|
||||
}
|
||||
};
|
||||
|
||||
const isNameOwned = nameStrings.includes(row.name);
|
||||
|
||||
const isNameBuying = pendingBuyNameStrings.includes(row.name);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableCell>{row.name}</TableCell>
|
||||
<TableCell>{row.salePrice}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
disabled={isPrimaryNameForSale}
|
||||
disabled={isPrimaryNameForSale || isNameOwned || isNameBuying}
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={() => handleBuy(row.name)}
|
||||
@ -198,10 +204,20 @@ export const ForSaleTable = ({
|
||||
handleSort,
|
||||
isPrimaryNameForSale,
|
||||
}: ForSaleTable) => {
|
||||
const setNames = useSetAtom(namesAtom);
|
||||
const [names, setNames] = useAtom(namesAtom);
|
||||
const setNamesForSale = useSetAtom(forSaleAtom);
|
||||
const setPendingTxs = useSetAtom(pendingTxsAtom);
|
||||
const [pendingTxs, setPendingTxs] = useAtom(pendingTxsAtom);
|
||||
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 (
|
||||
<Paper
|
||||
sx={{
|
||||
@ -223,7 +239,9 @@ export const ForSaleTable = ({
|
||||
setNames,
|
||||
setNamesForSale,
|
||||
isPrimaryNameForSale,
|
||||
t
|
||||
t,
|
||||
nameStrings,
|
||||
pendingBuyNameStrings
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
@ -63,6 +63,7 @@ import { TFunction } from 'i18next';
|
||||
interface NameData {
|
||||
name: string;
|
||||
isSelling?: boolean;
|
||||
forceUpdateState?: number;
|
||||
}
|
||||
|
||||
const getNameQueue = new RequestQueueWithPromise(2);
|
||||
@ -212,15 +213,17 @@ function rowContent(
|
||||
);
|
||||
return;
|
||||
}
|
||||
const loadId = showLoading(
|
||||
t('core:update_name.responses.loading', {
|
||||
postProcess: 'capitalizeFirstChar',
|
||||
})
|
||||
);
|
||||
let loadId = null;
|
||||
|
||||
try {
|
||||
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');
|
||||
|
||||
const res = await qortalRequest({
|
||||
action: 'UPDATE_NAME',
|
||||
newName: response,
|
||||
@ -264,13 +267,15 @@ function rowContent(
|
||||
return;
|
||||
}
|
||||
showError(
|
||||
t('core:update_name.responses.loading', {
|
||||
t('core:update_name.responses.error', {
|
||||
postProcess: 'capitalizeFirstChar',
|
||||
})
|
||||
);
|
||||
console.log('error', error);
|
||||
} finally {
|
||||
dismissToast(loadId);
|
||||
if (loadId) {
|
||||
dismissToast(loadId);
|
||||
}
|
||||
}
|
||||
|
||||
// Your logic here
|
||||
@ -285,16 +290,17 @@ function rowContent(
|
||||
);
|
||||
return;
|
||||
}
|
||||
const loadId = showLoading(
|
||||
t('core:sell_name.responses.loading', {
|
||||
postProcess: 'capitalizeFirstChar',
|
||||
})
|
||||
);
|
||||
let loadId = null;
|
||||
try {
|
||||
if (name === primaryName) {
|
||||
await modalFunctions.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')
|
||||
throw new Error(
|
||||
t('core:sell_name.responses.error3', {
|
||||
@ -347,13 +353,15 @@ function rowContent(
|
||||
);
|
||||
console.log('error', error);
|
||||
} finally {
|
||||
dismissToast(loadId);
|
||||
if (loadId) {
|
||||
dismissToast(loadId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = async (name: string) => {
|
||||
const loadId = showLoading(
|
||||
t('core:cancel_name.responses.error2', {
|
||||
t('core:cancel_name.responses.loading', {
|
||||
postProcess: 'capitalizeFirstChar',
|
||||
})
|
||||
);
|
||||
@ -381,7 +389,7 @@ function rowContent(
|
||||
};
|
||||
});
|
||||
showSuccess(
|
||||
t('core:cancel_name.responses.error2', {
|
||||
t('core:cancel_name.responses.success', {
|
||||
postProcess: 'capitalizeFirstChar',
|
||||
})
|
||||
);
|
||||
@ -391,12 +399,14 @@ function rowContent(
|
||||
return;
|
||||
}
|
||||
showError(
|
||||
t('core:cancel_name.responses.error2', {
|
||||
t('core:cancel_name.responses.error', {
|
||||
postProcess: 'capitalizeFirstChar',
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
dismissToast(loadId);
|
||||
if (loadId) {
|
||||
dismissToast(loadId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -408,8 +418,20 @@ function rowContent(
|
||||
display: 'flex',
|
||||
gap: '5px',
|
||||
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 && (
|
||||
<Tooltip
|
||||
title={t('core:tooltips.primary_name', {
|
||||
@ -467,6 +489,7 @@ function rowContent(
|
||||
color="error"
|
||||
size="small"
|
||||
onClick={() => handleCancel(row.name)}
|
||||
variant="contained"
|
||||
disabled={isNameCurrentlyDoingATx}
|
||||
>
|
||||
{t('core:actions.cancel_sell', {
|
||||
@ -495,6 +518,7 @@ export const NameTable = ({ names, primaryName }: NameTableProps) => {
|
||||
const [namesForSale, setNamesForSale] = useAtom(forSaleAtom);
|
||||
const [pendingTxs] = useAtom(pendingTxsAtom);
|
||||
const { t } = useTranslation(['core']);
|
||||
const [forceUpdateState, forceUpdate] = useState(0);
|
||||
|
||||
const modalFunctions = useModal<{ name: string }>();
|
||||
const modalFunctionsUpdateName = useModal();
|
||||
@ -504,15 +528,20 @@ export const NameTable = ({ names, primaryName }: NameTableProps) => {
|
||||
|
||||
const setPendingTxs = useSetAtom(pendingTxsAtom);
|
||||
|
||||
const triggerRerender = useCallback(() => {
|
||||
forceUpdate((n) => n + 1);
|
||||
}, []);
|
||||
|
||||
const namesToDisplay = useMemo(() => {
|
||||
const namesForSaleString = namesForSale.map((item) => item.name);
|
||||
return names.map((name) => {
|
||||
return {
|
||||
name: name.name,
|
||||
isSelling: namesForSaleString.includes(name.name),
|
||||
forceUpdateState,
|
||||
};
|
||||
});
|
||||
}, [names, namesForSale]);
|
||||
}, [names, namesForSale, forceUpdateState]);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
@ -596,7 +625,11 @@ export const NameTable = ({ names, primaryName }: NameTableProps) => {
|
||||
<UpdateNameModal modalFunctionsUpdateName={modalFunctionsUpdateName} />
|
||||
)}
|
||||
{modalFunctionsAvatar?.isShow && (
|
||||
<AvatarModal modalFunctionsAvatar={modalFunctionsAvatar} />
|
||||
<AvatarModal
|
||||
modalFunctionsAvatar={modalFunctionsAvatar}
|
||||
triggerRerender={triggerRerender}
|
||||
forceUpdateState={forceUpdateState}
|
||||
/>
|
||||
)}
|
||||
{modalFunctionsSellName?.isShow && (
|
||||
<SellNameModal modalFunctionsSellName={modalFunctionsSellName} />
|
||||
@ -612,8 +645,14 @@ interface PickedAvatar {
|
||||
|
||||
interface AvatarModalProps {
|
||||
modalFunctionsAvatar: ModalFunctionsAvatar;
|
||||
triggerRerender: () => void;
|
||||
forceUpdateState?: number;
|
||||
}
|
||||
const AvatarModal = ({ modalFunctionsAvatar }: AvatarModalProps) => {
|
||||
const AvatarModal = ({
|
||||
modalFunctionsAvatar,
|
||||
triggerRerender,
|
||||
forceUpdateState,
|
||||
}: AvatarModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { setHasAvatar } = usePendingTxs();
|
||||
const forceRefresh = useSetAtom(forceRefreshAtom);
|
||||
@ -651,6 +690,7 @@ const AvatarModal = ({ modalFunctionsAvatar }: AvatarModalProps) => {
|
||||
})
|
||||
);
|
||||
modalFunctionsAvatar.onOk(undefined);
|
||||
triggerRerender();
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
showError(error?.message);
|
||||
@ -699,10 +739,10 @@ const AvatarModal = ({ modalFunctionsAvatar }: AvatarModalProps) => {
|
||||
height: '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}
|
||||
>
|
||||
<CircularProgress />
|
||||
{modalFunctionsAvatar?.data?.name?.charAt(0)}
|
||||
</Avatar>
|
||||
)}
|
||||
{pickedAvatar?.base64 && (
|
||||
@ -1021,7 +1061,7 @@ interface SellNameModalProps {
|
||||
|
||||
const SellNameModal = ({ modalFunctionsSellName }: SellNameModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [price, setPrice] = useState(0);
|
||||
const [price, setPrice] = useState<number | string>(0);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@ -1044,10 +1084,24 @@ const SellNameModal = ({ modalFunctionsSellName }: SellNameModalProps) => {
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
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}
|
||||
type="number"
|
||||
placeholder={t('core:new_name.choose_price', {
|
||||
placeholder={t('core:sell_name.choose_price', {
|
||||
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