Skip to content

Commit

Permalink
Merge pull request #110 from ar-io/develop
Browse files Browse the repository at this point in the history
Release to Production
  • Loading branch information
kunstmusik authored Nov 14, 2024
2 parents 857c82c + 169e0d1 commit ccdc6fd
Show file tree
Hide file tree
Showing 57 changed files with 1,712 additions and 492 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ dist-id.txt
dist-manifest.csv
dist-manifest.json
package-lock.json
*.tsbuildinfo

17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.4.0] - 2024-11-14

### Added

* View Pending Withdrawals on Staking page and support cancelling pending withdrawals as well as performing expedited withdrawals
* View Changelog in app by clicking version number in sidebar

### Updated

* Staking page top cards now show balance, amount staking + pending withdrawals, and rewards earned last 14 epochs and last epoch

### Changed

* Updated header style of cards
* Observations: Updated to use arweave.net for reference domain when generating observation report
* Observe: Default to using prescribed names

## [1.3.0] - 2024-10-21

### Added
Expand Down
2 changes: 1 addition & 1 deletion jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"^@src/(.*)$": "<rootDir>/src/$1"
},
"transform": {
"^.+\\.(ts|tsx|js|jsx|mjs)$": ["ts-jest", { "useESM": true }]
"^.+\\.(ts|tsx|js|jsx|mjs)$": ["babel-jest"]
},
"transformIgnorePatterns": [
"/node_modules/(?!arbundles|arweave-wallet-connector|@permaweb|@dha-team|@ar.io).+\\.js$"
Expand Down
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ar-io/network-portal",
"private": true,
"version": "1.3.0",
"version": "1.4.0",
"type": "module",
"scripts": {
"build": "yarn clean && tsc --build tsconfig.build.json && NODE_OPTIONS=--max-old-space-size=32768 vite build",
Expand All @@ -20,7 +20,7 @@
"deploy": "yarn build && permaweb-deploy --ant-process ${DEPLOY_ANT_PROCESS_ID}"
},
"dependencies": {
"@ar.io/sdk": "2.3.2",
"@ar.io/sdk": "2.4.0",
"@fontsource/rubik": "^5.0.19",
"@headlessui/react": "^1.7.19",
"@radix-ui/react-tooltip": "^1.0.7",
Expand All @@ -34,11 +34,13 @@
"axios-retry": "^4.0.0",
"base64-arraybuffer": "^1.0.2",
"better-react-mathjax": "^2.0.3",
"dayjs": "^1.11.13",
"dexie": "^4.0.8",
"dexie-react-hooks": "^1.1.7",
"fflate": "^0.8.2",
"loglevel": "^1.9.1",
"lottie-react": "^2.4.0",
"markdown-to-jsx": "^7.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
Expand All @@ -54,6 +56,7 @@
"@commitlint/cli": "^17.6.7",
"@commitlint/config-conventional": "^17.6.7",
"@sentry/vite-plugin": "^0.7.2",
"@tailwindcss/typography": "^0.5.15",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
Expand Down Expand Up @@ -100,9 +103,9 @@
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7",
"ts-jest": "^29.1.2",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.2.2",
"typescript": "^5.6.3",
"vite": "^5.1.0",
"vite-bundle-visualizer": "^1.0.1",
"vite-plugin-node-polyfills": "^0.21.0",
Expand Down
16 changes: 10 additions & 6 deletions src/components/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import { ChevronDownIcon } from './icons';

type DropdownProps = {
options: { label: string; value: string }[];
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
value: string;
tightPadding?: boolean;
};

const Dropdown = ({
options,
onChange,
value,
}: {
options: { label: string; value: string }[];
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
value: string;
}) => {
tightPadding = false,
}: DropdownProps) => {
return (
<div className="relative w-fit min-w-fit">
<select
className="cursor-pointer appearance-none rounded-xl bg-transparent py-4 pl-4 pr-10 text-mid outline-none"
className={`cursor-pointer appearance-none rounded-xl bg-transparent ${!tightPadding && 'py-4 pl-4'} pr-10 text-mid outline-none`}
onChange={onChange}
value={value}
>
Expand Down
48 changes: 38 additions & 10 deletions src/components/GlobalDataProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import { AO_CU_URL, log } from '@src/constants';
import { useEffectOnce } from '@src/hooks/useEffectOnce';
import { useGlobalState } from '@src/store';
import { cleanupDbCache } from '@src/store/db';
import { ReactElement, useEffect } from 'react';

const GlobalDataProvider = ({
children,
}: {
children: ReactElement;
}) => {
const twoMinutes = 120000;
// Time to wait in ms to check if the AO CU URL is congested
const CONGESTION_WINDOW = 5000;
const TWO_MINUTES = 120000;

const GlobalDataProvider = ({ children }: { children: ReactElement }) => {

const setBlockHeight = useGlobalState((state) => state.setBlockHeight);
const setCurrentEpoch = useGlobalState((state) => state.setCurrentEpoch);
const setTicker = useGlobalState((state) => state.setTicker);
const arweave = useGlobalState((state) => state.arweave);
const arioReadSDK = useGlobalState((state) => state.arIOReadSDK);
const setAoCongested = useGlobalState((state) => state.setAoCongested);

useEffectOnce(() => {
const update = async () => {
// perform this first as retrieving the current epic takes some time
const {Ticker} = await arioReadSDK.getInfo();
// perform this first as retrieving the current epic takes some time
const { Ticker } = await arioReadSDK.getInfo();
setTicker(Ticker);

const currentEpoch = await arioReadSDK.getCurrentEpoch();
setCurrentEpoch(currentEpoch);

if(currentEpoch?.epochIndex) {
if (currentEpoch?.epochIndex) {
cleanupDbCache(currentEpoch.epochIndex);
}
};
Expand All @@ -34,15 +35,42 @@ const GlobalDataProvider = ({
});

useEffect(() => {
// Block Height Updater
const updateBlockHeight = async () => {
const blockHeight = await (await arweave.blocks.getCurrent()).height;
setBlockHeight(blockHeight);
};
updateBlockHeight();
const interval = setInterval(updateBlockHeight, twoMinutes);
const interval = setInterval(updateBlockHeight, TWO_MINUTES);

// AO congestion checker: Checks CU URL every 30 seconds and if it takes longer than 5 seconds will
// dispatch a warning to the user

const checkAoCongestion = () => {
const startTime = Date.now();
fetch(AO_CU_URL, { method: 'HEAD' })
.then((res) => {
const endTime = Date.now();
if (!res.ok) {
log.error('AO CU URL is down');
setAoCongested(true);
} else if (endTime - startTime > CONGESTION_WINDOW) {
setAoCongested(true);
}
})
.catch((error) => {
log.error('AO CU URL is down', error);
setAoCongested(true);
});
};

checkAoCongestion();
const congestionInterval = setInterval(checkAoCongestion, 30000);

return () => {
clearInterval(interval);
clearInterval(congestionInterval);
setAoCongested(false);
};
});

Expand Down
109 changes: 71 additions & 38 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,94 @@
import { mIOToken } from '@ar.io/sdk/web';
import { NBSP } from '@src/constants';
import useEpochCountdown from '@src/hooks/useEpochCountdown';
import useGateways from '@src/hooks/useGateways';
import useProtocolBalance from '@src/hooks/useProtocolBalance';
import { useGlobalState } from '@src/store';
import { formatWithCommas } from '@src/utils';
import { ReactNode } from 'react';
import Placeholder from './Placeholder';
import Profile from './Profile';
import { mIOToken } from '@ar.io/sdk/web';

interface HeaderItemProps {
value?: ReactNode;
label: string;
loading?: boolean;

leftPadding?: boolean;
}

const HeaderItem = ({
value,
label,
loading = false,
leftPadding = true,
}: HeaderItemProps) => {
return (
<div
className={`inline-flex h-[2.375rem] flex-col items-start justify-start gap-1 border-r ${leftPadding ? 'px-6' : 'pr-6'} dark:border-transparent-100-8`}
>
<div className="text-xs text-high">
{loading ? (
<Placeholder className="h-[1.0625rem]" />
) : value !== undefined ? (
typeof value === 'number' ? (
value.toLocaleString('en-US')
) : (
value
)
) : (
NBSP
)}
</div>
<div className="pt-1 text-xs leading-none text-low">{label}</div>
</div>
);
};

const Header = () => {
const blockHeight = useGlobalState((state) => state.blockHeight);
const currentEpoch = useGlobalState((state) => state.currentEpoch);
const epochCountdown = useEpochCountdown();
const ticker = useGlobalState((state) => state.ticker);
const { isLoading: gatewaysLoading, data: gateways } = useGateways();

const { data: protocolBalance } = useProtocolBalance();

return (
<header className="mt-6 flex h-[4.5rem] rounded-xl border py-4 pl-6 pr-4 leading-[1.4] dark:border-transparent-100-8 dark:bg-grey-1000 dark:text-grey-300">
<div className="inline-flex h-[2.375rem] flex-col items-start justify-start gap-1 border-r pr-6 dark:border-transparent-100-8">
<div className="text-xs text-high">
{currentEpoch?.epochIndex !== undefined
? currentEpoch.epochIndex.toLocaleString('en-US')
: NBSP}
</div>
<div className="pt-1 text-xs leading-none text-low">AR.IO EPOCH</div>
</div>
<div className="inline-flex h-[2.375rem] flex-col items-start justify-start gap-1 border-r px-6 dark:border-transparent-100-8">
<div className="text-xs text-high">
{blockHeight ? blockHeight.toLocaleString('en-US') : NBSP}
</div>
<div className="pt-1 text-xs leading-none text-low">ARWEAVE BLOCK</div>
</div>
<div className="inline-flex h-[2.375rem] flex-col items-start justify-start gap-1 border-r px-6 dark:border-transparent-100-8">
<div className="text-xs text-high">
{gatewaysLoading ? (
<Placeholder className="h-[1.0625rem]" />
) : gateways ? (
Object.values(gateways).filter((g) => g.status === 'joined').length
) : (
NBSP
)}
</div>
<div className="pt-1 text-xs leading-none text-low">GATEWAYS</div>
</div>

<div className="inline-flex h-[2.375rem] flex-col items-start justify-start gap-1 border-r px-6 dark:border-transparent-100-8">
<div className="text-xs text-high">
{protocolBalance == undefined ? (
<Placeholder className="h-[1.0625rem]" />
) : (
<HeaderItem
value={currentEpoch?.epochIndex.toLocaleString('en-US')}
label="AR.IO EPOCH"
loading={!currentEpoch}
leftPadding={false}
/>
<HeaderItem
value={epochCountdown}
label="NEXT EPOCH"
loading={!currentEpoch}
/>
<HeaderItem
value={blockHeight?.toLocaleString('en-US')}
label="ARWEAVE BLOCK"
loading={!blockHeight}
/>
<HeaderItem
value={gateways ? Object.keys(gateways).length : undefined}
label="GATEWAYS"
loading={gatewaysLoading}
/>
<HeaderItem
value={
protocolBalance ? (
<div>
{formatWithCommas(new mIOToken(protocolBalance).toIO().valueOf())} {ticker}
{formatWithCommas(new mIOToken(protocolBalance).toIO().valueOf())}{' '}
{ticker}
</div>
)}
</div>
<div className="pt-1 text-xs leading-none text-low">PROTOCOL BALANCE</div>
</div>
) : undefined
}
label="PROTOCOL BALANCE"
loading={!protocolBalance}
/>
<div className="grow" />
<div className="content-center">
<Profile />
Expand Down
37 changes: 37 additions & 0 deletions src/components/LabelValueRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const LabelValueRow = ({
label,
value,
className,
isLink = false,
rightIcon,
}: {
label: string;
value: string;
isLink?: boolean;
className?: string;
rightIcon?: React.ReactNode;
}) => {
return (
<div className={`flex items-center text-[0.8125rem] ${className}`}>
<div className="text-left text-low">{label}</div>
<div className="grow"></div>
{isLink && value !== '-' ? (
<a
className="text-gradient"
href={`https://${value}`}
target="_blank"
rel="noreferrer"
>
{value}
</a>
) : (
<div className="flex items-center gap-1 text-left text-low">
{value}
{rightIcon}
</div>
)}
</div>
);
};

export default LabelValueRow;
Loading

0 comments on commit ccdc6fd

Please sign in to comment.