Skip to content

Commit

Permalink
Merge pull request #14 from novasamatech/feat/dashboard-layout
Browse files Browse the repository at this point in the history
Feat: dashboard layout
  • Loading branch information
sokolova-an authored Dec 7, 2023
2 parents ce65d48 + bf2b840 commit d1b58e9
Show file tree
Hide file tree
Showing 34 changed files with 542 additions and 65 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@polkadot/util": "^12.2.1",
"@polkadot/util-crypto": "^12.2.1",
"autoprefixer": "10.4.15",
"bignumber.js": "^9.1.2",
"classnames": "^2.3.2",
"crypto-js": "^4.2.0",
"framer-motion": "^10.16.5",
Expand All @@ -34,7 +35,7 @@
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@testing-library/react": "^14.1.2",
"@types/crypto-js": "^4.1.3",
"@types/jest": "^29.5.6",
"@types/node": "20.6.2",
Expand Down
25 changes: 25 additions & 0 deletions public/images/Buy.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
11 changes: 11 additions & 0 deletions public/images/KSM.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions public/images/Receive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions public/images/Send.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions public/images/WND.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions public/images/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/common/chainRegistry/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ export type Asset = {
precision: number;
};

export type AssetWithBalance = Asset & {
name: string;
balance: string;
assetId: number;
symbol: string;
precision: number;
};

export type Chain = {
chainId: ChainId;
name: string;
Expand Down
19 changes: 19 additions & 0 deletions src/common/providers/contextProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { createContext, useContext, useState } from 'react';
import { HexString } from '../types';

export interface IContext {
publicKey?: HexString;
setPublicKey: React.Dispatch<React.SetStateAction<HexString | undefined>>;
}

export const GlobalContext = createContext<IContext>({ setPublicKey: () => {} });

export const GlobalStateProvider = ({ children }: { children: React.ReactNode }) => {
const [publicKey, setPublicKey] = useState<HexString>();

const value = { publicKey, setPublicKey };

return <GlobalContext.Provider value={value}>{children}</GlobalContext.Provider>;
};

export const useGlobalContext = () => useContext(GlobalContext);
124 changes: 124 additions & 0 deletions src/common/utils/balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import BigNumber from 'bignumber.js';

const ZERO_BALANCE = '0';

type FormattedBalance = {
formattedValue: string;
suffix: string;
decimalPlaces: number;
};

const enum Suffix {
MILLIONS = 'M',
BILLIONS = 'B',
TRILLIONS = 'T',
}

export const enum Decimal {
SMALL_NUMBER = 5,
BIG_NUMBER = 2,
}

// format balance from spektr

export const formatBalance = (balance = '0', precision = 0): FormattedBalance => {
const BNWithConfig = BigNumber.clone();
BNWithConfig.config({
// HOOK: for divide with decimal part
DECIMAL_PLACES: precision || Decimal.SMALL_NUMBER,
ROUNDING_MODE: BNWithConfig.ROUND_DOWN,
FORMAT: {
decimalSeparator: '.',
groupSeparator: '',
},
});
const TEN = new BNWithConfig(10);
const bnPrecision = new BNWithConfig(precision);
const bnBalance = new BNWithConfig(balance).div(TEN.pow(bnPrecision));
let divider = new BNWithConfig(1);
let decimalPlaces = 0;
let suffix = '';

if (bnBalance.lt(1)) {
decimalPlaces = Math.max(precision - balance.toString().length + 1, 5);
} else if (bnBalance.lt(10)) {
decimalPlaces = Decimal.SMALL_NUMBER;
} else if (bnBalance.lt(1_000_000)) {
decimalPlaces = Decimal.BIG_NUMBER;
} else if (bnBalance.lt(1_000_000_000)) {
decimalPlaces = Decimal.BIG_NUMBER;
divider = TEN.pow(new BNWithConfig(6));
suffix = Suffix.MILLIONS;
} else if (bnBalance.lt(1_000_000_000_000)) {
decimalPlaces = Decimal.BIG_NUMBER;
divider = TEN.pow(new BNWithConfig(9));
suffix = Suffix.BILLIONS;
} else {
decimalPlaces = Decimal.BIG_NUMBER;
divider = TEN.pow(new BNWithConfig(12));
suffix = Suffix.TRILLIONS;
}

return {
formattedValue: new BNWithConfig(bnBalance).div(divider).decimalPlaces(decimalPlaces).toFormat(),
suffix,
decimalPlaces,
};
};

const getDecimalPlaceForFirstNonZeroChar = (value: string, nonZeroDigits = 3) => {
const decimalPart = value.toString().split('.')[1];

return Math.max((decimalPart || '').search(/[1-9]/) + nonZeroDigits, 5);
};

export const formatFiatBalance = (balance = '0', precision = 0): FormattedBalance => {
if (Number(balance) === 0 || isNaN(Number(balance))) {
return { formattedValue: ZERO_BALANCE, suffix: '', decimalPlaces: 0 };
}
const BNWithConfig = BigNumber.clone();
BNWithConfig.config({
ROUNDING_MODE: BNWithConfig.ROUND_DOWN,
FORMAT: {
decimalSeparator: '.',
groupSeparator: '',
},
});

const bnPrecision = new BNWithConfig(precision);
const TEN = new BNWithConfig(10);
const bnBalance = new BNWithConfig(balance).div(TEN.pow(bnPrecision));

let divider = new BNWithConfig(1);
let suffix = '';
let decimalPlaces: number;

if (bnBalance.lt(1)) {
// if number has more than 7 digits in decimal part BigNumber.toString returns number in scientific notation
decimalPlaces = getDecimalPlaceForFirstNonZeroChar(bnBalance.toFixed());
} else if (bnBalance.lt(10)) {
decimalPlaces = Decimal.SMALL_NUMBER;
} else if (bnBalance.lt(1_000_000)) {
decimalPlaces = Decimal.BIG_NUMBER;
} else if (bnBalance.lt(1_000_000_000)) {
decimalPlaces = Decimal.BIG_NUMBER;
divider = TEN.pow(new BNWithConfig(6));
suffix = Suffix.MILLIONS;
} else if (bnBalance.lt(1_000_000_000_000)) {
decimalPlaces = Decimal.BIG_NUMBER;
divider = TEN.pow(new BNWithConfig(9));
suffix = Suffix.BILLIONS;
} else {
decimalPlaces = Decimal.BIG_NUMBER;
divider = TEN.pow(new BNWithConfig(12));
suffix = Suffix.TRILLIONS;
}

const bnFiatBalance = new BNWithConfig(bnBalance).div(divider).decimalPlaces(decimalPlaces).toFormat();

return {
formattedValue: bnFiatBalance,
suffix,
decimalPlaces,
};
};
4 changes: 3 additions & 1 deletion src/common/wallet/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ export const generateWalletMnemonic = (): string => {
return mnemonicGenerate();
};

export const createWallet = (mnemonic: string): void => {
export const createWallet = (mnemonic: string): HexString => {
const seed = mnemonicToMiniSecret(mnemonic);
const keypair = sr25519PairFromSeed(seed);
const publicKey: HexString = u8aToHex(keypair.publicKey);

localStorage.setItem(PUBLIC_KEY_STORE, publicKey);
secureLocalStorage.setItem(MNEMONIC_STORE, mnemonic);

return publicKey;
};

export const backupMnemonic = (mnemonic: string, password: string): void => {
Expand Down
30 changes: 30 additions & 0 deletions src/components/AssetsList/Asset.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { cnTw } from '@/common/utils/twMerge';
import { formatBalance } from '@/common/utils/balance';
import Icon from '../Icon/Icon';
import { CaptionText } from '../Typography';

type Props = {
value: string;
asset: any;
className?: string;
showIcon?: boolean;
imgClassName?: string;
wrapperClassName?: string;
};

export const AssetBalance = ({ value, asset, className, imgClassName }: Props) => {
const { precision, symbol } = asset;
const { formattedValue, suffix } = formatBalance(value, precision);

console.log(suffix, formattedValue);

return (
<span className={cnTw('flex items-center gap-x-2', className)}>
<Icon name={symbol} size={40} alt="logo" className={imgClassName} />
<CaptionText>{symbol}</CaptionText>
<CaptionText>
{formattedValue} {suffix}
</CaptionText>
</span>
);
};
21 changes: 21 additions & 0 deletions src/components/AssetsList/AssetsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AssetWithBalance } from '@/common/chainRegistry/types';
import { AssetBalance } from './Asset';

type Props = {
assets: AssetWithBalance[];
};

const AssetsList = ({ assets }: Props) => (
<div className="flex flex-col gap-6 mt-4">
{assets.map((asset) => (
<AssetBalance
asset={asset}
value={asset.balance}
className="grid grid-cols-[50px,1fr,auto] items-center"
key={asset.assetId}
/>
))}
</div>
);

export default AssetsList;
18 changes: 16 additions & 2 deletions src/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FootnoteText } from '../Typography';
import AllIcons, { IconNames } from './types';
import Image from 'next/image';

Expand All @@ -6,9 +7,10 @@ type Props = {
size?: number;
className?: string;
alt?: string;
text?: string;
};

const Icon = ({ name, size = 24, className, alt = '' }: Props) => {
const Icon = ({ name, size = 24, className, text, alt = '' }: Props) => {
let iconPath = AllIcons[name];

if (!iconPath) {
Expand All @@ -17,7 +19,19 @@ const Icon = ({ name, size = 24, className, alt = '' }: Props) => {
return <div style={{ width: size, height: size, borderRadius: 10, backgroundColor: 'lightgrey' }} />;
}

return (
return text ? (
<div className="text-center">
<Image
className={className}
src={`/images/${iconPath}`}
alt={alt}
width={size}
height={size}
data-testid={`${name}-img`}
/>
<FootnoteText as="span">{text}</FootnoteText>
</div>
) : (
<Image
className={className}
src={`/images/${iconPath}`}
Expand Down
Loading

0 comments on commit d1b58e9

Please sign in to comment.