some fixes

This commit is contained in:
kandrusyak
2022-12-06 16:16:12 +03:00
parent 8bf83aaa5b
commit 78f48b240b
12 changed files with 238 additions and 73 deletions

View File

@@ -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 />} />

View File

@@ -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>

View 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
View 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>
);
});

View File

@@ -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}

View File

@@ -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 */}

View File

@@ -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">

View File

@@ -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)
}
/>
);
})}

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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,
})),
},
};