some fixes
This commit is contained in:
@@ -19,6 +19,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import Delegates from './pages/services/Delegates';
|
||||
import VPNServers from './pages/services/VPNServers';
|
||||
import VPNPrepare from './pages/services/VPNPrepare';
|
||||
import { NotificationContainer } from './components/NotificationContainer';
|
||||
|
||||
const App = observer(() => {
|
||||
const location = useLocation();
|
||||
@@ -39,6 +40,7 @@ const App = observer(() => {
|
||||
return (
|
||||
<>
|
||||
{store.loading && <LoadingOverlay />}
|
||||
<NotificationContainer />
|
||||
<Routes>
|
||||
<Route exact path="/" element={<Onboarding1 />} />
|
||||
<Route path="/digitalId/profile-id" element={<ProfileId />} />
|
||||
|
||||
@@ -89,9 +89,9 @@ function DropdownTransaction({ align, value, onChange }) {
|
||||
<li>
|
||||
<span
|
||||
className="font-medium text-sm text-slate-600 hover:text-slate-800 flex items-center py-1 px-3 cursor-pointer"
|
||||
onClick={() => changeValue('Unvoiting')}
|
||||
onClick={() => changeValue('Unlocking')}
|
||||
>
|
||||
Unvoiting
|
||||
Unlocking
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
14
src/components/NotificationContainer.jsx
Normal file
14
src/components/NotificationContainer.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Toast } from './Toast2';
|
||||
import { store } from '../store/store';
|
||||
|
||||
export const NotificationContainer = observer(() => (
|
||||
<div className="absolute z-50 top-2 right-2 gap-2">
|
||||
{store.notifications.map((e) => (
|
||||
<Toast type={e.type} key={e.id} id={e.id}>
|
||||
{e.message}
|
||||
</Toast>
|
||||
))}
|
||||
</div>
|
||||
));
|
||||
90
src/components/Toast2.jsx
Normal file
90
src/components/Toast2.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { store } from '../store/store';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
export const Toast = observer(({ children, className, type, id }) => {
|
||||
const typeIcon = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return (
|
||||
<svg
|
||||
className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm0 12c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zm1-3H7V4h2v5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<svg
|
||||
className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3.5 10.1l-1.4 1.4L8 9.4l-2.1 2.1-1.4-1.4L6.6 8 4.5 5.9l1.4-1.4L8 6.6l2.1-2.1 1.4 1.4L9.4 8l2.1 2.1z" />
|
||||
</svg>
|
||||
);
|
||||
case 'success':
|
||||
return (
|
||||
<svg
|
||||
className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zM7 11.4L3.6 8 5 6.6l2 2 4-4L12.4 6 7 11.4z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg
|
||||
className="w-4 h-4 shrink-0 fill-current opacity-80 mt-[3px] mr-3"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path d="M8 0C3.6 0 0 3.6 0 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm1 12H7V7h2v5zM8 6c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const typeColor = (type) => {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
return 'bg-amber-100 border-amber-200 text-amber-600';
|
||||
case 'error':
|
||||
return 'bg-rose-100 border-rose-200 text-rose-600';
|
||||
case 'success':
|
||||
return 'bg-emerald-100 border-emerald-200 text-emerald-600';
|
||||
default:
|
||||
return 'bg-indigo-100 border-indigo-200 text-indigo-500';
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timerId = setTimeout(() => store.removeNotification(id), 3000);
|
||||
return () => clearTimeout(timerId);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div
|
||||
className={`inline-flex min-w-80 px-4 py-2 rounded-sm text-sm border ${typeColor(
|
||||
type
|
||||
)}`}
|
||||
>
|
||||
<div className="flex w-full justify-between items-start">
|
||||
<div className="flex">
|
||||
{typeIcon(type)}
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
<button
|
||||
className="opacity-70 hover:opacity-80 ml-3 mt-[3px]"
|
||||
onClick={() => store.removeNotification(id)}
|
||||
>
|
||||
<div className="sr-only">Close</div>
|
||||
<svg className="w-4 h-4 fill-current">
|
||||
<path d="M7.95 6.536l4.242-4.243a1 1 0 111.415 1.414L9.364 7.95l4.243 4.242a1 1 0 11-1.415 1.415L7.95 9.364l-4.243 4.243a1 1 0 01-1.414-1.415L6.536 7.95 2.293 3.707a1 1 0 011.414-1.414L7.95 6.536z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import Sidebar from '../../partials/Sidebar';
|
||||
import Header from '../../partials/Header';
|
||||
@@ -10,7 +10,7 @@ import TransactionPanel from '../../partials/finance/TransactionPanel';
|
||||
import { Filters } from '../../components/Filters';
|
||||
|
||||
const filters = {
|
||||
'View All': '',
|
||||
'View All': 'all',
|
||||
Voted: 'Unvote',
|
||||
Pending: 'Pending',
|
||||
};
|
||||
@@ -22,17 +22,24 @@ const Delegates = observer(() => {
|
||||
|
||||
const [lockedFor, setLockedFor] = useState('Voiting');
|
||||
|
||||
const lockedLabel = Object.values(
|
||||
lockedFor === 'Voiting' ? store.processedVotes : store.accountLockedVotes
|
||||
const delegates = useMemo(() => {
|
||||
if (filters[currentFilter] === 'all') return store.delegates;
|
||||
return store.delegates.filter((e) => e.status === filters[currentFilter]);
|
||||
}, [store.delegates, currentFilter]);
|
||||
|
||||
const lockedLabel = (
|
||||
lockedFor === 'Voiting'
|
||||
? Object.values(store.accountSentVotes)
|
||||
: Object.values(store.accountLockedVotesCanReturn)
|
||||
.flat()
|
||||
.map((e) => e.amount)
|
||||
)
|
||||
.reduce((sum, e) => sum + e, 0n)
|
||||
.toString();
|
||||
|
||||
const [fetchOffset, setFetchOffset] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
store.fetchDelegates(fetchOffset);
|
||||
}, [fetchOffset]);
|
||||
store.fetchDelegates(0);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
@@ -72,20 +79,21 @@ const Delegates = observer(() => {
|
||||
|
||||
{/* Table */}
|
||||
<TransactionsTable
|
||||
data={store.delegates}
|
||||
data={delegates}
|
||||
lockedFor={lockedFor}
|
||||
rowClick={(delegate) => setCurrentDelegate(delegate)}
|
||||
/>
|
||||
|
||||
{/* Pagination */}
|
||||
<div className="mt-8">
|
||||
<PaginationClassic
|
||||
count={store.delegatesMeta.count}
|
||||
limit={store.delegatesMeta.limit}
|
||||
offset={store.delegatesMeta.offset}
|
||||
onNextPage={() => setFetchOffset((prev) => prev + 10)}
|
||||
onPrevPage={() => setFetchOffset((prev) => prev - 10)}
|
||||
/>
|
||||
</div>
|
||||
{/*/!* Pagination *!/*/}
|
||||
{/*<div className="mt-8">*/}
|
||||
{/* <PaginationClassic*/}
|
||||
{/* count={store.delegatesMeta.count}*/}
|
||||
{/* limit={store.delegatesMeta.limit}*/}
|
||||
{/* offset={store.delegatesMeta.offset}*/}
|
||||
{/* onNextPage={() => setFetchOffset((prev) => prev + 10)}*/}
|
||||
{/* onPrevPage={() => setFetchOffset((prev) => prev - 10)}*/}
|
||||
{/* />*/}
|
||||
{/*</div>*/}
|
||||
|
||||
<TransactionPanel
|
||||
transactionPanelOpen={!!currentDelegate}
|
||||
|
||||
@@ -26,6 +26,8 @@ const VPNPrepare = observer(() => {
|
||||
if (store.canVPN) navigate('/services/vpn/servers');
|
||||
}, [store.canVPN]);
|
||||
|
||||
if (store.loading && !store.canVPN) return null;
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
|
||||
@@ -95,7 +95,7 @@ PersistentKeepalive = 25
|
||||
<label
|
||||
className="relative block cursor-pointer text-left w-full"
|
||||
onClick={() => setSelectedServer(i)}
|
||||
key={e.publicKey}
|
||||
key={e.serverPublickKey}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
@@ -107,7 +107,7 @@ PersistentKeepalive = 25
|
||||
<div className="p-4 rounded border border-slate-200 hover:border-slate-300 shadow-sm duration-150 ease-in-out">
|
||||
<div className="grid grid-cols-12 items-center gap-x-2">
|
||||
{/* Card */}
|
||||
<div className="col-span-6 order-1 sm:order-none sm:col-span-3 flex items-center space-x-4 lg:sidebar-expanded:col-span-6 xl:sidebar-expanded:col-span-3">
|
||||
<div className="col-span-6 order-1 sm:order-none flex items-center space-x-4 ">
|
||||
<ReactCountryFlag
|
||||
countryCode={e.country}
|
||||
svg
|
||||
@@ -121,13 +121,13 @@ PersistentKeepalive = 25
|
||||
</div>
|
||||
</div>
|
||||
{/* Card limits */}
|
||||
<div className="col-span-6 order-1 sm:order-none sm:col-span-4 text-right sm:text-center lg:sidebar-expanded:col-span-6 xl:sidebar-expanded:col-span-6">
|
||||
<div className="col-span-3 order-1 sm:order-none text-right sm:text-center">
|
||||
<div className="text-sm">
|
||||
{e.transferSum} / 50.00 GB
|
||||
</div>
|
||||
</div>
|
||||
{/* Card status */}
|
||||
<div className="col-span-6 order-2 sm:order-none sm:col-span-2 text-right lg:sidebar-expanded:hidden xl:sidebar-expanded:block">
|
||||
<div className="col-span-3 order-2 sm:order-none text-right">
|
||||
{e?.state ? (
|
||||
<div className="text-xs inline-flex font-medium bg-emerald-100 text-emerald-600 rounded-full text-center px-2.5 py-1">
|
||||
Active
|
||||
@@ -150,7 +150,7 @@ PersistentKeepalive = 25
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
{servers?.length > 0 && (
|
||||
{servers?.length > 0 && servers?.[selectedServer]?.state && (
|
||||
<div>
|
||||
<div className="lg:sticky lg:top-16 bg-slate-50 lg:overflow-x-hidden lg:overflow-y-auto no-scrollbar lg:shrink-0 border-t lg:border-t-0 lg:border-l border-slate-200 lg:w-[390px] lg:h-[calc(100vh-64px)]">
|
||||
<div className="py-8 px-4 lg:px-8 h-full">
|
||||
|
||||
@@ -4,7 +4,7 @@ import TransactionItem from './TransactionsTableItem';
|
||||
import Image01 from '../../images/transactions-image-01.svg';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const TransactionsTable = observer(({ data, rowClick }) => {
|
||||
const TransactionsTable = observer(({ data, rowClick, lockedFor }) => {
|
||||
return (
|
||||
<div className="bg-white">
|
||||
<div>
|
||||
@@ -30,7 +30,9 @@ const TransactionsTable = observer(({ data, rowClick }) => {
|
||||
<div className="font-semibold text-center"></div>
|
||||
</th>
|
||||
<th className="px-4 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="font-semibold text-center">Sent Votes</div>
|
||||
<div className="font-semibold text-center">
|
||||
{lockedFor === 'Voiting' ? 'Sent Votes' : 'Blocked votes'}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -42,14 +44,25 @@ const TransactionsTable = observer(({ data, rowClick }) => {
|
||||
key={delegate.address}
|
||||
id={delegate.address}
|
||||
image={Image01}
|
||||
balance={delegate.token.balance}
|
||||
name={delegate.dpos.delegate.username || delegate.address}
|
||||
total={delegate.dpos.delegate.totalVotesReceived}
|
||||
hasSevice={false}
|
||||
balance={delegate?.token?.balance}
|
||||
name={delegate.dpos?.delegate?.username || delegate.address}
|
||||
total={delegate.dpos?.delegate?.totalVotesReceived || ''}
|
||||
hasSevice={
|
||||
delegate.dpos?.delegate?.username === 'delegate_0'
|
||||
}
|
||||
date={delegate.date}
|
||||
displayReturn={lockedFor === 'Unlocking'}
|
||||
status={delegate.status}
|
||||
amount={store.accountSentVotes[delegate.address] || 0}
|
||||
handleClick={() => rowClick(delegate)}
|
||||
amount={
|
||||
(lockedFor === 'Voiting'
|
||||
? store.accountSentVotes[delegate.address]
|
||||
: store.accountLockedVotesCanReturnSum[
|
||||
delegate.address
|
||||
]) || ''
|
||||
}
|
||||
handleClick={() =>
|
||||
lockedFor === 'Voiting' && rowClick(delegate)
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -43,14 +43,6 @@ const TransactionsTableItem = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className="font-medium text-slate-800">{props.name}</div>
|
||||
{store.accountLockedVotesCanReturn[props.id] && (
|
||||
<span
|
||||
className="truncate font-medium text-indigo-500 group-hover:text-indigo-600 ml-2"
|
||||
onClick={clickReturn}
|
||||
>
|
||||
Return votes
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
@@ -72,21 +64,36 @@ const TransactionsTableItem = observer((props) => {
|
||||
<div className="text-center">{props.balance}/idn</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-center">{props.total}/idn</div>
|
||||
<div className="text-center">
|
||||
{props.total}
|
||||
{props.total && '/idn'}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap">
|
||||
<div className="text-center">
|
||||
{props.displayReturn ? (
|
||||
store.accountLockedVotesCanReturn[props.id] && (
|
||||
<span
|
||||
className="truncate font-medium text-indigo-500 group-hover:text-indigo-600 ml-2"
|
||||
onClick={clickReturn}
|
||||
>
|
||||
Return votes
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
className={`text-xs inline-flex font-medium rounded-full justify-center px-2.5 py-1 ${statusColor()}`}
|
||||
style={{ minWidth: '84px' }}
|
||||
>
|
||||
{props.status}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-2 first:pl-5 last:pr-5 py-3 whitespace-nowrap w-px">
|
||||
<div className={`text-center font-medium ${amountColor()}`}>
|
||||
{props.amount.toString()}/idn
|
||||
{props.amount.toString()}
|
||||
{props.amount && '/idn'}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -64,7 +64,12 @@ function request(method, url, headers = {}, token, body) {
|
||||
requestOptions.headers.Authorization = null;
|
||||
}
|
||||
setTimeout(() => controller.abort(), 15000);
|
||||
return fetch(prepareUrl(url), requestOptions);
|
||||
return fetch(prepareUrl(url), requestOptions).then((res) => {
|
||||
if (res.status > 400) {
|
||||
store.addNotification(new Date().getTime(), 'error', res.statusText);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
function get(url, headers, attempts = 3) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import { statusMap } from '../shared/statusMap';
|
||||
import { generateSvgAvatar } from '../images/GenerateOnboardingSvg/GenerateSvg';
|
||||
import { decryptedData } from '../utils/decryptedData';
|
||||
import {
|
||||
bytesToMbytes,
|
||||
encryptAccountData,
|
||||
encryptSharedData,
|
||||
formatBytes,
|
||||
@@ -55,6 +54,8 @@ class Store {
|
||||
},
|
||||
};
|
||||
|
||||
_notifications = [];
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {});
|
||||
|
||||
@@ -93,7 +94,7 @@ class Store {
|
||||
.then((r) => this.fetchVPNServersSuccess(r));
|
||||
}
|
||||
|
||||
fetchDelegates(offset = 0, limit = 10) {
|
||||
fetchDelegates(offset = 0, limit = 100) {
|
||||
fetchWrapper
|
||||
.getAuth(`delegates?limit=${limit}&offset=${offset}`)
|
||||
.then((r) => this.fetchDelegatesSuccess(r));
|
||||
@@ -253,10 +254,9 @@ class Store {
|
||||
this.passPhrase
|
||||
);
|
||||
|
||||
const { amount, unvoteHeight } =
|
||||
this.accountLockedVotesCanReturn[delegateAddress];
|
||||
const unlockObjects = this.accountLockedVotesCanReturn[delegateAddress];
|
||||
|
||||
const signedTx = builder.unlock(delegateAddress, amount, unvoteHeight);
|
||||
const signedTx = builder.unlock(delegateAddress, unlockObjects);
|
||||
|
||||
if (signedTx) {
|
||||
fetchWrapper
|
||||
@@ -507,15 +507,27 @@ class Store {
|
||||
.reduce(
|
||||
(acc, e) => ({
|
||||
...acc,
|
||||
[e.delegateAddress]: {
|
||||
[e.delegateAddress]: [
|
||||
{
|
||||
...acc[e.delegateAddress],
|
||||
amount: BigInt(e.amount || 0) / 100000000n,
|
||||
unvoteHeight: e.unvoteHeight,
|
||||
},
|
||||
],
|
||||
}),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
get accountLockedVotesCanReturnSum() {
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.accountLockedVotesCanReturn).map(([key, val]) => [
|
||||
key,
|
||||
val.reduce((sum, e) => sum + e.amount, 0n),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
get accountFeaturesMap() {
|
||||
return this.accountFeatures.reduce(
|
||||
(acc, item) => ({
|
||||
@@ -644,7 +656,7 @@ class Store {
|
||||
...delegate.dpos.delegate,
|
||||
totalVotesReceived: Number(
|
||||
BigInt(delegate.dpos.delegate.totalVotesReceived) / 100000000n
|
||||
).toString(),
|
||||
),
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -653,6 +665,22 @@ class Store {
|
||||
get delegatesMeta() {
|
||||
return this._delegates.meta;
|
||||
}
|
||||
|
||||
get notifications() {
|
||||
return this._notifications;
|
||||
}
|
||||
|
||||
addNotification(id, type, message) {
|
||||
this._notifications.push({
|
||||
id,
|
||||
type,
|
||||
message,
|
||||
});
|
||||
}
|
||||
|
||||
removeNotification(id) {
|
||||
this._notifications = this._notifications.filter((e) => e.id !== id);
|
||||
}
|
||||
}
|
||||
|
||||
export const store = new Store();
|
||||
|
||||
@@ -197,7 +197,7 @@ export const generateTransaction = (
|
||||
delegateAddress,
|
||||
amount
|
||||
),
|
||||
unlock: (delegateAddress, amount, unvoteHeight) =>
|
||||
unlock: (delegateAddress, unlockObjects) =>
|
||||
generateUnlockTransaction(
|
||||
nonce,
|
||||
senderPublicKey,
|
||||
@@ -205,8 +205,7 @@ export const generateTransaction = (
|
||||
passPhrase,
|
||||
fee,
|
||||
delegateAddress,
|
||||
amount,
|
||||
unvoteHeight
|
||||
unlockObjects
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -379,8 +378,7 @@ const generateUnlockTransaction = (
|
||||
passPhrase = '',
|
||||
fee = BigInt(500000),
|
||||
delegateAddress = '',
|
||||
amount = 0n,
|
||||
unvoteHeight = 0
|
||||
unlockObjects = []
|
||||
) => {
|
||||
const tx = {
|
||||
moduleID: 5,
|
||||
@@ -389,13 +387,11 @@ const generateUnlockTransaction = (
|
||||
senderPublicKey: Buffer.from(senderPublicKey, 'hex'),
|
||||
fee,
|
||||
asset: {
|
||||
unlockObjects: [
|
||||
{
|
||||
unlockObjects: unlockObjects.map((e) => ({
|
||||
delegateAddress: Buffer.from(delegateAddress, 'hex'),
|
||||
amount: amount * 100000000n,
|
||||
unvoteHeight,
|
||||
},
|
||||
],
|
||||
amount: e.amount * 100000000n,
|
||||
unvoteHeight: e.unvoteHeight,
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user