Skip to content

Commit

Permalink
feat(vault): staking
Browse files Browse the repository at this point in the history
  • Loading branch information
vihangpatil committed Oct 21, 2024
1 parent dee5903 commit 99468b1
Show file tree
Hide file tree
Showing 29 changed files with 1,463 additions and 190 deletions.
3 changes: 3 additions & 0 deletions apps/vault/src/assets/chevron-right-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions apps/vault/src/assets/home-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions apps/vault/src/assets/vault-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions apps/vault/src/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import HomeIcon from '../assets/home-icon.svg';
import ChevronRightIcon from '../assets/chevron-right-icon.svg';
import Image from 'next/image';
import Link from 'next/link';

interface BreadcrumbsProps {
pages: Page[];
}

interface Page {
name: string;
href: string;
current?: boolean;
}

const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ pages }) => {
return (
<nav aria-label="Breadcrumb" className="px-4 mt-4 flex">
<ol role="list" className="flex items-center space-x-4">
<li>
<div>
<a href="/" className="text-gray-400 hover:text-gray-500">
<Image className="ps-2 w-8 inline" src={HomeIcon} alt={'Home'} />
<span className="sr-only">Home</span>
</a>
</div>
</li>
{pages.map((page) => (
<li key={page.name}>
<div className="flex items-center">
<Image
className="ps-2 h-5 w-5 inline"
src={ChevronRightIcon}
alt={'>'}
/>
<Link
href={page.href}
aria-current={page.current ? 'page' : undefined}
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
>
{page.name}
</Link>
</div>
</li>
))}
</ol>
</nav>
);
};

export default Breadcrumbs;
40 changes: 40 additions & 0 deletions apps/vault/src/components/CopyText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import Image from 'next/image';
import CopyIcon from '../assets/copy-icon.png';
import React, { useState } from 'react';
import { InfoAlert } from '@/components';

interface CopyTextProps {
text: string;
alertMessage?: string;
}

const CopyText: React.FC<CopyTextProps> = ({ text, alertMessage }) => {
const [alertText, setAlertText] = useState<string | null>(null);
return (
<>
<InfoAlert
show={alertText != null}
hide={() => setAlertText(null)}
alertText={alertText || ''}
className="my-3"
/>
<div className="flex flex-row no-wrap">
<div className="grow-0 outline rounded p-1">
<span className="font-mono">{text}</span>
<Image
className="ps-2 w-8 inline"
src={CopyIcon}
alt={'Copy'}
onClick={() =>
navigator.clipboard
.writeText(text || '')
.then(() => setAlertText(alertMessage ?? 'Copied'))
}
/>
</div>
</div>
</>
);
};

export default CopyText;
43 changes: 43 additions & 0 deletions apps/vault/src/components/NavigationBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { useRouter } from 'next/router';

const tabs = [
{ name: 'Home', href: '/' },
{ name: 'Staking', href: '/staking' },
{ name: 'Staking positions', href: '/staking/positions' },
];

function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ');
}

const NavigationBar: React.FC = () => {
const router = useRouter();
const currentTab = tabs.find((tab) => tab.href === router.pathname);
const tabName = currentTab ? currentTab.name : undefined;
return (
<div>
<div className="border-b border-gray-200">
<nav aria-label="Tabs" className="-mb-px flex space-x-8">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
aria-current={tab.name == tabName ? 'page' : undefined}
className={classNames(
tab.name == tabName
? 'text-label-dark-primary border-b-2 border-brand-light-primary'
: 'text-label-dark-secondary',
'whitespace-nowrap h-10 p-2 text-sm font-medium'
)}
>
{tab.name}
</a>
))}
</nav>
</div>
</div>
);
};

export default NavigationBar;
8 changes: 1 addition & 7 deletions apps/vault/src/components/VaultHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import config from '@/firebase/config';
import { Header } from 'ui';
import vaultLogo from '../assets/vault-logo.svg';
import Link from 'next/link';
import { useRouter } from 'next/router';

interface VaultHeaderProps {}

// TODO: upgrade the secondary header here with the ui one
const AuthHeader = dynamic(
Expand All @@ -26,10 +23,7 @@ const AuthHeader = dynamic(
}
);

const VaultHeader: React.FC<VaultHeaderProps> = ({}) => {
const router = useRouter();
const isLanding =
router.pathname.startsWith('/products/') || router.pathname === '/';
const VaultHeader: React.FC = () => {
return (
<>
<AuthHeader
Expand Down
7 changes: 7 additions & 0 deletions apps/vault/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export { default as VaultHeader } from './VaultHeader';
export { default as VaultFooter } from './VaultFooter';
export { default as NavigationBar } from './NavigationBar';
export { default as Breadcrumbs } from './Breadcrumbs';
export { default as CopyText } from './CopyText';
export { default as VaultStakingPositionStatusLabel } from './staking/VaultStakingPositionStatusLabel';
export { default as StakingAssetsTable } from './staking/VaultStakingAssetsTable';
export { default as VaultStakeActionModal } from './staking/VaultStakeActionModal';
export { default as VaultStakingPositionsTable } from './staking/positions/VaultStakingPositionsTable';
export { default as BalanceCard } from './vaultAssets/BalanceCard';
export { default as DonutChart } from './vaultAssets/DonutChart';
export { default as CurrencyDropdown } from './vaultAssets/CurrencyDropdown';
Expand Down
159 changes: 159 additions & 0 deletions apps/vault/src/components/staking/VaultStakeActionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { BasicButton, Modal } from 'ui';
import React, { useState } from 'react';
import {
getStakingConstraints,
validateStakeAction,
} from '@/utils/stakingRules';

interface StakeActionModalProps {
vaultStakeAssetId: string;
available: number;
hide: () => void;
className?: string;
}

const VaultStakeActionModal: React.FC<StakeActionModalProps> = ({
vaultStakeAssetId,
available,
hide,
className,
}) => {
let extraInfo;
if (vaultStakeAssetId.startsWith('ETH')) {
extraInfo = 'Staked ETH has to be less than 32, or multiple of 32.';
}
const { minimumStakeAmount, maximumStakeAmount, minimumAvailable } =
getStakingConstraints(available);
const [stakeAmount, setStakeAmount] = useState<number>(maximumStakeAmount);
const { balanceAfterStaking, stakeError, balanceAfterError } =
validateStakeAction(vaultStakeAssetId, available, stakeAmount);
const balanceAfterClassName = balanceAfterError
? 'border-2 border-brand-light-primary disabled:bg-brand-light-primary text-label-dark-primary'
: 'border disabled:ui-bg-default-systemGrey-light-2';
const stakeAmountClassName = stakeError
? 'border-brand-light-primary bg-brand-light-primary text-label-dark-primary'
: '';
const valid = !!balanceAfterError && !!stakeError;
return (
<div className={className}>
<Modal backdrop={true} open={true} onClose={() => {}} size={'small'}>
<div className="space-y-12 sm:space-y-16">
<div>
<h1 className="block text-label-light-secondary text-[32px]">
Stake
</h1>
<p className="mt-1 max-w-2xl text-sm leading-6 text-gray-600">
Stake your <span className="font-semibold text-[18px]">ETH</span>{' '}
or <span className="font-bold text-[18px]">SOL</span> assets.
<br />
Support to stake{' '}
<span className="font-bold text-[18px]">MATIC</span> is coming
soon.
</p>

<div className="pt-5">
<div>
<label className="font-medium">Vault Asset</label>
</div>
<div className="p-2">
<input
className="border px-1 w-full disabled:ui-bg-default-systemGrey-light-2"
type="text"
value={vaultStakeAssetId}
disabled={true}
/>
</div>
<div>
<label className="font-medium">Available Balance</label>
</div>
<div className="p-2">
<input
className="border px-1 w-full disabled:ui-bg-default-systemGrey-light-2"
type="text"
value={available}
disabled={true}
/>
</div>
<div>
<label className="font-medium">Balance after staking</label>{' '}
(minimum: {minimumAvailable})
</div>
<div className="p-2">
<input
className={`px-1 w-full ${balanceAfterClassName}`}
type="text"
value={balanceAfterStaking}
disabled={true}
/>
<div className="text-brand-light-primary text-[16px]">
{balanceAfterError ?? ''}
</div>
</div>
<div className="pt-4">
<label htmlFor="last-name" className="font-medium">
Stake Amount
</label>{' '}
(minimum: {minimumStakeAmount})
</div>
<div className="p-2">
<input
className={`border-2 px-1 w-full ${stakeAmountClassName}`}
type="number"
value={stakeAmount}
onChange={(e) => {
if (e.target.value === '') {
setStakeAmount(0);
} else {
setStakeAmount(parseFloat(e.target.value));
}
}}
required={true}
/>
<div className="text-brand-light-primary text-[16px]">
{stakeError ?? ''}
</div>
</div>
</div>
<div className="mt-5">
<h1 className="block text-label-light-secondary text-[24px]">
Requirements
</h1>
</div>
<div className="ms-5">
<ul className="list-disc">
{extraInfo && (
<li className="font-normal text-label-light-primary">
{extraInfo}
</li>
)}
<li>
<span className="font-normal text-label-light-primary">
Minimum stake amount:
</span>{' '}
<span className="font-mono">{minimumStakeAmount}</span>
</li>
<li>
<span className="font-normal text-label-light-primary">
Minimum balance to be maintained:
</span>{' '}
<span className="font-mono">{minimumAvailable}</span>
</li>
</ul>
</div>
</div>
</div>

<div className="mt-6 flex items-center justify-end gap-x-6">
<BasicButton variant="secondary" size="medium" onClick={hide}>
Cancel
</BasicButton>
<BasicButton variant="primary" size="medium" disabled={!valid}>
Submit
</BasicButton>
</div>
</Modal>
</div>
);
};

export default VaultStakeActionModal;
Loading

0 comments on commit 99468b1

Please sign in to comment.