Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,20 @@
"Unfavorite": "Unfavorite",
"share link": "Share Link",
"Mulitsig Transaction Details": "Mulitsig Transaction Details",
"Please be sure to double-check the mulitsig transaction progress and parameters carefully before approving.": "Please be sure to double-check the mulitsig transaction progress and parameters carefully before approving."
"Please be sure to double-check the mulitsig transaction progress and parameters carefully before approving.": "Please be sure to double-check the mulitsig transaction progress and parameters carefully before approving.",
"api_key": {
"title": "Set up API key",
"button": "Advanced settings",
"desc_storage": "Your API key is stored only in this browser (not uploaded to Subscan servers).",
"desc_security": "Don't save it on public/shared devices. Keep it secure.",
"desc_features": "After setup, you can enable features like History and auto-fetching Call Data.",
"desc_get_key": "Get an API key:",
"label": "API Key",
"placeholder": "Enter your Subscan API key",
"current_set": "API key is currently set",
"saved": "API key saved",
"deleted": "API key deleted",
"invalid_key": "Invalid API key. Please check and try again.",
"invalid_key_or_network": "Invalid API key or unsupported network. Please check and try again."
}
}
17 changes: 16 additions & 1 deletion public/locales/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,20 @@
"unfavorite": "取消收藏",
"share link": "分享链接",
"Mulitsig Transaction Details": "多签交易详情",
"Please be sure to double-check the mulitsig transaction progress and parameters carefully before approving.": "在批准前,请务必仔细检查多签交易进度和参数。"
"Please be sure to double-check the mulitsig transaction progress and parameters carefully before approving.": "在批准前,请务必仔细检查多签交易进度和参数。",
"api_key": {
"title": "配置 API Key",
"button": "高级设置",
"desc_storage": "API Key 仅保存在本地浏览器中(不上传 Subscan 服务器)。",
"desc_security": "请勿在公共设备上保存,并妥善保管以防泄露。",
"desc_features": "配置后可启用:历史记录、自动获取 Call Data 等功能。",
"desc_get_key": "获取 API Key:",
"label": "API Key",
"placeholder": "请输入 Subscan API Key",
"current_set": "当前已设置 API Key",
"saved": "API Key 已保存",
"deleted": "API Key 已删除",
"invalid_key": "API Key 无效,请检查后重试。",
"invalid_key_or_network": "API Key 无效或网络不受支持,请检查后重试。"
}
}
11 changes: 11 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import subscanLogo from 'src/assets/images/subscan_logo.png';
import { Footer } from './components/Footer';
import { HeadAccounts } from './components/HeadAccounts';
import { DownIcon } from './components/icons';
import { ApiKeyModal } from './components/modals/ApiKeyModal';
import { SelectNetworkModal } from './components/modals/SelectNetworkModal';
import Status from './components/Status';
import { chains } from './config/chains';
Expand Down Expand Up @@ -54,6 +55,7 @@ function App() {
}, [rpc]);

const [selectNetworkModalVisible, setSelectNetworkModalVisible] = useState(false);
const [apiKeyModalVisible, setApiKeyModalVisible] = useState(false);

const openExplorer = () => {
if (networkConfig?.explorerHostName) {
Expand Down Expand Up @@ -89,6 +91,14 @@ function App() {

{networkStatus === 'success' && <HeadAccounts />}

<Button
className="flex justify-between items-center px-2 mr-2"
title={t('api_key.title')}
onClick={() => setApiKeyModalVisible(true)}
>
{t('api_key.button')}
</Button>

<Button
className="flex justify-between items-center px-2"
onClick={() => {
Expand Down Expand Up @@ -127,6 +137,7 @@ function App() {
{apiError && <Alert message={apiError} type="error" showIcon closable className="fixed top-24 right-20" />}

<SelectNetworkModal visible={selectNetworkModalVisible} onCancel={() => setSelectNetworkModalVisible(false)} />
<ApiKeyModal visible={apiKeyModalVisible} onCancel={() => setApiKeyModalVisible(false)} />
</>
);
}
Expand Down
29 changes: 22 additions & 7 deletions src/components/ExtrinsicRecords.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ReloadOutlined } from '@ant-design/icons';
import { KeyringAddress } from '@polkadot/ui-keyring/types';
import { Space, Spin, Tabs } from 'antd';
import { isNumber } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { useApi, useMultisigRecords } from '../hooks';
Expand Down Expand Up @@ -248,18 +248,33 @@ export function ExtrinsicRecords() {
}
}, [cancelledPage]); // eslint-disable-line react-hooks/exhaustive-deps

const fetchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

// eslint-disable-next-line complexity
const handleChangeTab = (key: string) => {
setTabKey(key);
if (key === 'inProgress') {
queryInProgress();
} else if (key === 'confirmed') {
fetchConfirmed();
} else if (key === 'cancelled') {
fetchCancelled();
if (fetchTimerRef.current) {
clearTimeout(fetchTimerRef.current);
}
fetchTimerRef.current = setTimeout(() => {
if (key === 'inProgress') {
queryInProgress();
} else if (key === 'confirmed') {
fetchConfirmed();
} else if (key === 'cancelled') {
fetchCancelled();
}
}, 300);
};

Comment thread
carlhong marked this conversation as resolved.
useEffect(() => {
return () => {
if (fetchTimerRef.current) {
clearTimeout(fetchTimerRef.current);
fetchTimerRef.current = null;
}
};
}, []);
const refreshData = () => {
queryInProgress();
fetchConfirmed();
Expand Down
15 changes: 3 additions & 12 deletions src/components/TxApprove.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function TxApprove({
const [getApproveTx] = useMultiApprove();
const { queueExtrinsic } = useContext(StatusContext);
const [getUnapprovedInjectedList] = useUnapprovedAccounts();
const { setIsPageLock, queryInProgress, refreshConfirmedAccount } = useMultisigContext();
const { setIsPageLock, queryInProgress, refreshCounts } = useMultisigContext();
const unapprovedAddresses = getUnapprovedInjectedList(entry);
const memberPairs = (account.meta?.addressPair ?? []) as AddressPair[];
const injectedMemberAccounts: string[] = memberPairs
Expand Down Expand Up @@ -55,7 +55,7 @@ export function TxApprove({
makeSure(txSpy)(null);
queryInProgress();
setTimeout(() => {
refreshConfirmedAccount();
refreshCounts();
// eslint-disable-next-line no-magic-numbers
}, 10000);
},
Expand All @@ -73,16 +73,7 @@ export function TxApprove({
}
);
},
[
availableAccounts,
getApproveTx,
onOperation,
queryInProgress,
queueExtrinsic,
refreshConfirmedAccount,
setIsPageLock,
txSpy,
]
[availableAccounts, getApproveTx, onOperation, queryInProgress, queueExtrinsic, refreshCounts, setIsPageLock, txSpy]
);

if (!entry.callHash || !entry.callData) {
Expand Down
5 changes: 2 additions & 3 deletions src/components/WalletState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ export function WalletState(props: WalletStateProps) {
const history = useHistory();
const { network, api, networkConfig } = useApi();
const { multisigAccount, changeMultisigAccount } = props;
const { inProgress, queryInProgress, confirmedAccount, refreshConfirmedAccount, fetchInProgress } =
useMultisigContext();
const { inProgress, queryInProgress, confirmedAccount, refreshCounts, fetchInProgress } = useMultisigContext();
const [isAccountsDisplay, setIsAccountsDisplay] = useState<boolean>(false);
const [isExtrinsicDisplay, setIsExtrinsicDisplay] = useState(false);
const [isTransferDisplay, setIsTransferDisplay] = useState(false);
Expand Down Expand Up @@ -132,7 +131,7 @@ export function WalletState(props: WalletStateProps) {

const id = setInterval(() => {
tick();
refreshConfirmedAccount();
refreshCounts();
}, LONG_DURATION);

return () => clearInterval(id);
Expand Down
139 changes: 139 additions & 0 deletions src/components/modals/ApiKeyModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import { CloseOutlined } from '@ant-design/icons';
import axios from 'axios';
import { Button, Input, Modal, message } from 'antd';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { getLinkColor, getThemeColor } from 'src/config';
import { useApi } from 'src/hooks';
import { readStorage, updateStorage } from 'src/utils/helper/storage';

interface ApiKeyModalProps {
visible: boolean;
onCancel: () => void;
}

const FALLBACK_SUBSCAN_URL = 'https://polkadot.api.subscan.io';

async function validateApiKey(subscanBaseUrl: string, apiKey: string): Promise<void> {
const { data } = await axios.post(
`${subscanBaseUrl}/api/now`,
{},
{ headers: { 'X-API-Key': apiKey }, timeout: 10000 }
);
// Subscan returns code 0 on success; non-zero means auth failure or error
if (data?.code !== 0) {
throw new Error(`code:${data?.code}`);
}
}

// eslint-disable-next-line complexity
export const ApiKeyModal = ({ visible, onCancel }: ApiKeyModalProps) => {
const { t } = useTranslation();
const { network, networkConfig } = useApi();
const mainColor = useMemo(() => getThemeColor(network), [network]);
const linkColor = useMemo(() => getLinkColor(network), [network]);

const [apiKey, setApiKey] = useState(() => readStorage().subscanApiKey || '');
const [validating, setValidating] = useState(false);
const [validationError, setValidationError] = useState('');

const subscanUrl = networkConfig?.api?.subscan || FALLBACK_SUBSCAN_URL;
const currentKey = readStorage().subscanApiKey;

const handleSave = async () => {
const trimmed = apiKey.trim();

// clearing the key — no need to validate
if (!trimmed) {
updateStorage({ subscanApiKey: undefined });
message.success(t('api_key.saved'));
onCancel();
return;
}

setValidating(true);
setValidationError('');

try {
await validateApiKey(subscanUrl, trimmed);
updateStorage({ subscanApiKey: trimmed });
message.success(t('api_key.saved'));
onCancel();
} catch (err: any) {
setValidationError(t('api_key.invalid_key_or_network'));
} finally {
setValidating(false);
}
};

const handleDelete = () => {
updateStorage({ subscanApiKey: undefined });
setApiKey('');
setValidationError('');
message.success(t('api_key.deleted'));
onCancel();
};

return (
<Modal title={null} footer={null} visible={visible} destroyOnClose onCancel={onCancel} closable={false} width={480}>
<div>
<div className="flex items-center justify-between mb-4">
<div className="font-bold" style={{ color: mainColor, fontSize: '16px' }}>
{t('api_key.title')}
</div>
<CloseOutlined className="cursor-pointer" style={{ color: '#666666' }} onClick={onCancel} />
</div>

<div className="bg-divider mb-4" style={{ height: '1px' }} />

<ul className="mb-2 text-sm pl-4 list-disc" style={{ color: '#666666' }}>
<li>{t('api_key.desc_storage')}</li>
<li>{t('api_key.desc_security')}</li>
<li>{t('api_key.desc_features')}</li>
<li>
{t('api_key.desc_get_key')}{' '}
<a href="https://pro.subscan.io" target="_blank" rel="noopener noreferrer" style={{ color: linkColor }}>
pro.subscan.io
</a>
</li>
</ul>

<div className="mt-4 mb-2 font-bold text-black-800">{t('api_key.label')}</div>

<Input.Password
value={apiKey}
placeholder={t('api_key.placeholder')}
onChange={(e) => {
setApiKey(e.target.value);
setValidationError('');
}}
className={validationError ? 'ant-input-status-error' : ''}
/>

{validationError && (
<div className="mt-1 text-xs" style={{ color: '#ff4d4f' }}>
{validationError}
</div>
)}

{!validationError && currentKey && (
<div className="mt-2 text-xs" style={{ color: '#888' }}>
{t('api_key.current_set')}
</div>
)}

<div className="flex gap-3 mt-6">
<Button block style={{ color: mainColor }} loading={validating} onClick={handleSave}>
{t('save')}
</Button>

{currentKey && (
<Button block danger disabled={validating} onClick={handleDelete}>
{t('delete')}
</Button>
)}
</div>
</div>
</Modal>
);
};
2 changes: 1 addition & 1 deletion src/config/chains/assethub_kusama.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rpc": "wss://asset-hub-kusama-rpc.n.dwellir.com",
"api": {
"subql": "",
"subscan": "https://assethub-kusama.webapi.subscan.io"
"subscan": "https://assethub-kusama.api.subscan.io"
},
"category": "kusama",
"logo": "/image/assethub-kusama.png",
Expand Down
2 changes: 1 addition & 1 deletion src/config/chains/assethub_paseo.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rpc": "wss://asset-hub-paseo-rpc.n.dwellir.com",
"api": {
"subql": "",
"subscan": "https://assethub-paseo.webapi.subscan.io"
"subscan": "https://assethub-paseo.api.subscan.io"
},
"category": "paseo",
"logo": "/image/assethub-paseo.png",
Expand Down
2 changes: 1 addition & 1 deletion src/config/chains/assethub_polkadot.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rpc": "wss://asset-hub-polkadot-rpc.n.dwellir.com",
"api": {
"subql": "",
"subscan": "https://assethub-polkadot.webapi.subscan.io"
"subscan": "https://assethub-polkadot.api.subscan.io"
},
"category": "polkadot",
"logo": "/image/assethub-polkadot.png",
Expand Down
2 changes: 1 addition & 1 deletion src/config/chains/astar.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rpc": "wss://astar-rpc.n.dwellir.com",
"api": {
"subql": "",
"subscan": "https://astar.webapi.subscan.io"
"subscan": "https://astar.api.subscan.io"
},
"category": "polkadot",
"logo": "/image/astar.png",
Expand Down
2 changes: 1 addition & 1 deletion src/config/chains/bifrost.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rpc": "wss://hk.p.bifrost-rpc.liebi.com/ws",
"api": {
"subql": "",
"subscan": "https://bifrost.webapi.subscan.io"
"subscan": "https://bifrost.api.subscan.io"
},
"category": "polkadot",
"logo": "/image/bifrost.png",
Expand Down
2 changes: 1 addition & 1 deletion src/config/chains/coretime_kusama.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rpc": "wss://coretime-kusama-rpc.n.dwellir.com",
"api": {
"subql": "",
"subscan": "https://coretime-kusama.webapi.subscan.io"
"subscan": "https://coretime-kusama.api.subscan.io"
},
"category": "kusama",
"logo": "/image/coretime-kusama.png",
Expand Down
2 changes: 1 addition & 1 deletion src/config/chains/coretime_paseo.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rpc": "wss://sys.ibp.network/coretime-paseo",
"api": {
"subql": "",
"subscan": "https://coretime-paseo.webapi.subscan.io"
"subscan": "https://coretime-paseo.api.subscan.io"
},
"category": "paseo",
"logo": "/image/coretime-paseo.png",
Expand Down
2 changes: 1 addition & 1 deletion src/config/chains/coretime_polkadot.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"rpc": "wss://coretime-polkadot-rpc.n.dwellir.com",
"api": {
"subql": "",
"subscan": "https://coretime-polkadot.webapi.subscan.io"
"subscan": "https://coretime-polkadot.api.subscan.io"
},
"category": "polkadot",
"logo": "/image/coretime-polkadot.png",
Expand Down
Loading
Loading