diff --git a/.changeset/funny-moose-obey.md b/.changeset/funny-moose-obey.md new file mode 100644 index 000000000..45e8e2710 --- /dev/null +++ b/.changeset/funny-moose-obey.md @@ -0,0 +1,5 @@ +--- +'explorer': minor +--- + +All explorer pages now show details in their open graph preview images. diff --git a/.changeset/hot-chicken-wave.md b/.changeset/hot-chicken-wave.md new file mode 100644 index 000000000..246baeef6 --- /dev/null +++ b/.changeset/hot-chicken-wave.md @@ -0,0 +1,5 @@ +--- +'explorer': minor +--- + +Contracts now show a visual timeline and status. diff --git a/.changeset/thick-dingos-marry.md b/.changeset/thick-dingos-marry.md new file mode 100644 index 000000000..c3f4a0eaa --- /dev/null +++ b/.changeset/thick-dingos-marry.md @@ -0,0 +1,5 @@ +--- +'explorer': minor +--- + +The explorer no longer depends on Navigator. diff --git a/.changeset/witty-eggs-fail.md b/.changeset/witty-eggs-fail.md new file mode 100644 index 000000000..6581572c3 --- /dev/null +++ b/.changeset/witty-eggs-fail.md @@ -0,0 +1,5 @@ +--- +'explorer': minor +--- + +The explorer has been revamped and not supports searching for and viewing hosts. diff --git a/.eslintrc.json b/.eslintrc.json index 06cc47d9a..9ca2e830c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,12 +1,12 @@ { "root": true, "ignorePatterns": ["**/*"], - "plugins": ["@nrwl/nx"], + "plugins": ["@nx"], "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { - "@nrwl/nx/enforce-module-boundaries": [ + "@nx/enforce-module-boundaries": [ "error", { "enforceBuildableLibDependency": true, @@ -23,12 +23,12 @@ }, { "files": ["*.ts", "*.tsx"], - "extends": ["plugin:@nrwl/nx/typescript"], + "extends": ["plugin:@nx/typescript"], "rules": {} }, { "files": ["*.js", "*.jsx"], - "extends": ["plugin:@nrwl/nx/javascript"], + "extends": ["plugin:@nx/javascript"], "rules": {} } ] diff --git a/README.md b/README.md index ce22ecaad..acc768d68 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,6 @@ User interfaces for the Sia software. The latest Sia software takes a modular ap The [`renterd`](https://github.com/siafoundation/renterd) user interface, focused on renting functionality. -### [explorer-v1](apps/explorer-v1) - -![stability-mature](https://img.shields.io/badge/stability-mature-008000.svg) - -The `explorer-v1` user interface, a Sia blockchain explorer interface based on [Navigator](https://github.com/hakkane84/navigator-sia). Powers [explore.sia.tech](https://explore.sia.tech) and [zen.sia.tech](https://zen.sia.tech). - ### [hostd](apps/hostd) ![stability-beta](https://img.shields.io/badge/stability-beta-yellow.svg) @@ -32,6 +26,12 @@ The [`hostd`](https://github.com/siafoundation/hostd) user interface, focused on The [`walletd`](https://github.com/siafoundation/walletd) user interface, includes a wallet with support for hot, cold, and hardware wallets. +### [explorer](apps/explorer) + +![stability-mature](https://img.shields.io/badge/stability-mature-008000.svg) + +The `explorer` user interface, a Sia blockchain explorer interface that powers [explore.sia.tech](https://explore.sia.tech) and [zen.sia.tech](https://zen.sia.tech). + ## Libraries ![stability-wip](https://img.shields.io/badge/stability-work_in_progress-orange.svg) @@ -44,6 +44,8 @@ The Sia web libraries provide developers with convenient TypeScript SDKs for usi - [@siafoundation/react-renterd](libs/react-renterd) - React hooks for interacting with `renterd`. - [@siafoundation/react-hostd](libs/react-hostd) - React hooks for interacting with `hostd`. - [@siafoundation/react-walletd](libs/react-walletd) - React hooks for interacting with `walletd`. +- [@siafoundation/react-sia-central](libs/react-sia-central) - React hooks for interacting with the Sia Central API. +- [@siafoundation/sia-central](libs/sia-central) - Methods and types for interacting with the Sia Central API. - [@siafoundation/sia-js](libs/sia-js) - Core Sia types and library methods for v1 `siad`. - [@siafoundation/sia-nodejs](libs/sia-nodejs) - Sia NodeJS client for controlling a v1 `siad`. - [@siafoundation/design-system](libs/design-system) - React-based design system used across Sia apps and websites. diff --git a/apps/assets/jest.config.ts b/apps/assets/jest.config.ts index 28bc27a2a..6e7d3c0ee 100644 --- a/apps/assets/jest.config.ts +++ b/apps/assets/jest.config.ts @@ -2,14 +2,15 @@ export default { displayName: 'assets', - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.spec.json', - }, - }, + globals: {}, testEnvironment: 'node', transform: { - '^.+\\.[tj]s$': 'ts-jest', + '^.+\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], }, moduleFileExtensions: ['ts', 'js', 'html'], coverageDirectory: '../../coverage/apps/assets', diff --git a/apps/assets/project.json b/apps/assets/project.json index a05bee8ef..876aace28 100644 --- a/apps/assets/project.json +++ b/apps/assets/project.json @@ -5,7 +5,7 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/webpack:webpack", + "executor": "@nx/webpack:webpack", "outputs": ["{options.outputPath}"], "options": { "target": "node", @@ -34,7 +34,7 @@ } }, "serve": { - "executor": "@nrwl/js:node", + "executor": "@nx/js:node", "options": { "inspect": "inspect", "buildTarget": "assets:build" @@ -47,14 +47,14 @@ } }, "lint": { - "executor": "@nrwl/linter:eslint", + "executor": "@nx/linter:eslint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/assets/**/*.ts"] } }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/assets"], "options": { "jestConfig": "apps/assets/jest.config.ts", diff --git a/apps/assets/tsconfig.app.json b/apps/assets/tsconfig.app.json index ae96ef027..6828ac1d3 100644 --- a/apps/assets/tsconfig.app.json +++ b/apps/assets/tsconfig.app.json @@ -3,7 +3,12 @@ "compilerOptions": { "module": "commonjs", "outDir": "../../dist/out-tsc", - "types": ["node", "express"], + "types": [ + "node", + "express", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ], "esModuleInterop": true }, "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], diff --git a/apps/assets/tsconfig.spec.json b/apps/assets/tsconfig.spec.json index 9b2a121d1..b2a7c10ed 100644 --- a/apps/assets/tsconfig.spec.json +++ b/apps/assets/tsconfig.spec.json @@ -3,7 +3,12 @@ "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", - "types": ["jest", "node"] + "types": [ + "jest", + "node", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] }, "include": [ "jest.config.ts", diff --git a/apps/assets/webpack.config.js b/apps/assets/webpack.config.js index 9200831a7..e1d15c36b 100644 --- a/apps/assets/webpack.config.js +++ b/apps/assets/webpack.config.js @@ -1,4 +1,4 @@ -const { composePlugins, withNx } = require('@nrwl/webpack') +const { composePlugins, withNx } = require('@nx/webpack') // Nx plugins for webpack. module.exports = composePlugins(withNx(), (config) => { diff --git a/apps/crons/jest.config.ts b/apps/crons/jest.config.ts index 3592d4ed4..62112b959 100644 --- a/apps/crons/jest.config.ts +++ b/apps/crons/jest.config.ts @@ -2,14 +2,15 @@ export default { displayName: 'crons', - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.spec.json', - }, - }, + globals: {}, testEnvironment: 'node', transform: { - '^.+\\.[tj]s$': 'ts-jest', + '^.+\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], }, moduleFileExtensions: ['ts', 'js', 'html'], coverageDirectory: '../../coverage/apps/crons', diff --git a/apps/crons/project.json b/apps/crons/project.json index 557ab99f5..ddb6f504e 100644 --- a/apps/crons/project.json +++ b/apps/crons/project.json @@ -5,7 +5,7 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/webpack:webpack", + "executor": "@nx/webpack:webpack", "outputs": ["{options.outputPath}"], "options": { "target": "node", @@ -34,7 +34,7 @@ } }, "serve": { - "executor": "@nrwl/js:node", + "executor": "@nx/js:node", "options": { "inspect": "inspect", "buildTarget": "crons:build" @@ -47,14 +47,14 @@ } }, "lint": { - "executor": "@nrwl/linter:eslint", + "executor": "@nx/linter:eslint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["apps/crons/**/*.ts"] } }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/apps/crons"], "options": { "jestConfig": "apps/crons/jest.config.ts", diff --git a/apps/crons/tsconfig.app.json b/apps/crons/tsconfig.app.json index ae96ef027..6828ac1d3 100644 --- a/apps/crons/tsconfig.app.json +++ b/apps/crons/tsconfig.app.json @@ -3,7 +3,12 @@ "compilerOptions": { "module": "commonjs", "outDir": "../../dist/out-tsc", - "types": ["node", "express"], + "types": [ + "node", + "express", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ], "esModuleInterop": true }, "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], diff --git a/apps/crons/tsconfig.spec.json b/apps/crons/tsconfig.spec.json index 9b2a121d1..b2a7c10ed 100644 --- a/apps/crons/tsconfig.spec.json +++ b/apps/crons/tsconfig.spec.json @@ -3,7 +3,12 @@ "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", - "types": ["jest", "node"] + "types": [ + "jest", + "node", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] }, "include": [ "jest.config.ts", diff --git a/apps/crons/webpack.config.js b/apps/crons/webpack.config.js index 9200831a7..e1d15c36b 100644 --- a/apps/crons/webpack.config.js +++ b/apps/crons/webpack.config.js @@ -1,4 +1,4 @@ -const { composePlugins, withNx } = require('@nrwl/webpack') +const { composePlugins, withNx } = require('@nx/webpack') // Nx plugins for webpack. module.exports = composePlugins(withNx(), (config) => { diff --git a/apps/explorer-v1/components/ContentLayout.tsx b/apps/explorer-v1/components/ContentLayout.tsx deleted file mode 100644 index f8d020515..000000000 --- a/apps/explorer-v1/components/ContentLayout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Container, Panel } from '@siafoundation/design-system' -import React from 'react' - -type Props = { - panel: React.ReactNode - children?: React.ReactNode - className?: string -} - -export function ContentLayout({ panel, children, className }: Props) { - return ( - <> - -
- - {panel} - -
-
- - {children} - - - ) -} diff --git a/apps/explorer-v1/components/ContractConditionsSection.tsx b/apps/explorer-v1/components/ContractConditionsSection.tsx deleted file mode 100644 index 816be3ab7..000000000 --- a/apps/explorer-v1/components/ContractConditionsSection.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable react/no-unescaped-entities */ -import { NvgDatum, DatumProps } from './NvgDatum' -import { getContractConditions } from '../lib/transaction' -import { NvgContractEntity, NvgRevisionEntity } from '../config/navigatorTypes' -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, - Heading, - Label, -} from '@siafoundation/design-system' -import BigNumber from 'bignumber.js' - -type Props = { - entity: NvgContractEntity | NvgRevisionEntity -} - -export function ContractConditionsSection({ entity }: Props) { - const conditions = getContractConditions(entity) - - const success: DatumProps[] = [ - { - label: 'Returned allowance', - sc: new BigNumber(conditions.success.returnedAllowance), - }, - { - label: 'Payout + collateral', - sc: new BigNumber(conditions.success.payoutCollateral), - }, - ] - - const failure: DatumProps[] = [ - { - label: 'Returned allowance', - sc: new BigNumber(conditions.fail.returnedAllowance), - }, - { - label: 'Burnt collateral', - sc: new BigNumber(conditions.fail.burntCollateral), - }, - ] - - return ( - - - - Conditions - - -
-
- -
- {success.map((item) => ( - - ))} -
-
-
- -
- {failure.map((item) => ( - - ))} -
-
-
-
-
-
- ) -} diff --git a/apps/explorer-v1/components/Faucet/index.tsx b/apps/explorer-v1/components/Faucet/index.tsx deleted file mode 100644 index 0e6e1c48a..000000000 --- a/apps/explorer-v1/components/Faucet/index.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { - Container, - FaucetIcon, - Heading, - Panel, - Tabs, - TabsContent, - TabsList, - TabsTrigger, - Text, - webLinks, -} from '@siafoundation/design-system' -import { useCallback, useEffect, useState } from 'react' -import { FaucetFundForm } from './FaucetFundForm' -import { FaucetStatus } from './FaucetStatus' -import { Layout } from '../Layout' -import { isMainnet, networkName } from '../../config' -import { routes } from '../../config/routes' - -type Tab = 'fund' | 'status' - -export function Faucet() { - const title = 'Faucet' - const description = 'Sia testnet faucet' - const path = routes.faucet.index - const [id, setId] = useState('') - const [tab, setTab] = useState('fund') - - useEffect(() => { - if (isMainnet) { - window.location.replace(webLinks.explore.testnetFaucet) - } - }, []) - - const onDone = useCallback( - (id: string) => { - setId(id) - setTab('status') - }, - [setId, setTab] - ) - - return ( - - -
- -
- - - - {networkName} Faucet - setTab(val as Tab)} - className="w-full" - > - - Fund - Status - - - - - - - - -
-
-
-
-
- ) -} diff --git a/apps/explorer-v1/components/Home.tsx b/apps/explorer-v1/components/Home.tsx deleted file mode 100644 index 3f0c8e4bd..000000000 --- a/apps/explorer-v1/components/Home.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import { - Text, - EntityList, - BlockList, - Tooltip, -} from '@siafoundation/design-system' -import { - humanBytes, - humanDifficulty, - humanHashrate, - humanNumber, -} from '@siafoundation/sia-js' -import { useMemo } from 'react' -import { SWRResponse } from 'swr' -import { HomeSkeleton } from '../components/HomeSkeleton' -import { - getNvgEntityTypeInitials, - getNvgEntityTypeLabel, - NvgBlockEntity, -} from '../config/navigatorTypes' -import { useEntity } from '../hooks/useEntity' -import { useLanding } from '../hooks/useLanding' -import { useStatus } from '../hooks/useStatus' -import { routes } from '../config/routes' -import { ContentLayout } from './ContentLayout' -import { useSiaCentralHostsNetworkMetrics } from '@siafoundation/react-core' -import { siaCentralApi } from '../config' - -export function Home() { - const status = useStatus() - const landing = useLanding() - const metrics = useSiaCentralHostsNetworkMetrics({ - api: siaCentralApi, - }) - const height = status.data?.lastblock - const block = useEntity( - height ? String(height) : null - ) as SWRResponse - - const values = useMemo(() => { - if (!landing.data || !status.data || !block.data) { - return [] - } - const list = [ - { - label: 'Blockchain height', - value: ( -
- {humanNumber(status.data?.lastblock)} - {status.data && - status.data.consensusblock !== status.data.lastblock && ( - <> - - / - - - - {humanNumber(status.data?.consensusblock)} - - - - )} -
- ), - }, - { - label: 'Storage utilization', - value: ( -
- - - {humanBytes( - metrics.data?.totals.total_storage - - metrics.data?.totals.remaining_storage - )} - - - - / - - - - {humanBytes(metrics.data?.totals.total_storage)} - - -
- ), - }, - { - label: 'Registry utilization', - value: ( -
- - - {humanNumber( - metrics.data?.totals.total_registry_entries - - metrics.data?.totals.remaining_registry_entries, - { abbreviated: true, fixed: 2 } - )} - - - - / - - - - {humanNumber(metrics.data?.totals.total_registry_entries, { - abbreviated: true, - fixed: 2, - })} - - -
- ), - }, - { - label: 'Active hosts', - value: ( -
- - - {humanNumber(metrics.data?.totals.active_hosts)} - - - - / - - - - {humanNumber(metrics.data?.totals.total_hosts)} - - -
- ), - }, - { - label: 'Transactions', - value: ( -
- - - {humanNumber(status.data?.mempool)} - - - - / - - - - {humanNumber(status.data?.totalTx)} - - -
- ), - }, - { - label: 'Market cap', - value: humanNumber( - status.data?.consensusblock ? status.data.coinsupply : 0, - { abbreviated: true, fixed: 2, units: 'SC' } - ), - }, - { - label: 'Difficulty', - value: humanDifficulty(block.data?.data[1]?.Difficulty), - }, - { - label: 'Hash rate', - value: humanHashrate(block.data?.data[1]?.Hashrate), - }, - { - label: 'Version', - value: status.data?.version, - }, - ] - return list - }, [status, landing, block, metrics]) - - if (!landing.data || !status.data || !block.data) { - return - } - - return ( - - {values.map(({ label, value }) => ( -
- - {label} - - - {value} - -
- ))} - - } - > -
-
- ({ - height: block.Height, - timestamp: block.Timestamp * 1000, - miningPool: block.MiningPool, - href: routes.block.view.replace('[id]', String(block.Height)), - }))} - /> -
-
- ({ - hash: tx.TxHash, - height: tx.Height, - label: getNvgEntityTypeLabel('ScTx'), - initials: getNvgEntityTypeInitials('ScTx'), - href: routes.tx.view.replace('[id]', tx.TxHash), - avatarShape: 'circle', - }))} - /> -
-
- ({ - hash: tx.TxHash, - height: tx.Height, - label: getNvgEntityTypeLabel(tx.TxType), - initials: getNvgEntityTypeInitials(tx.TxType), - href: routes.tx.view.replace('[id]', tx.TxHash), - avatarShape: 'circle', - }))} - /> -
-
- ({ - hash: tx.TxHash, - height: tx.Height, - label: getNvgEntityTypeLabel(tx.TxType), - initials: getNvgEntityTypeInitials(tx.TxType), - href: routes.tx.view.replace('[id]', tx.TxHash), - avatarShape: 'circle', - }))} - /> -
-
-
- ) -} diff --git a/apps/explorer-v1/components/HomeSkeleton/index.tsx b/apps/explorer-v1/components/HomeSkeleton/index.tsx deleted file mode 100644 index c14bfbf35..000000000 --- a/apps/explorer-v1/components/HomeSkeleton/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Skeleton, EntityList, BlockList } from '@siafoundation/design-system' -import { ContentLayout } from '../ContentLayout' - -export function HomeSkeleton() { - return ( - - - - - - - - - - - - } - > - {/* */} -
- - - - -
- {/*
*/} -
- ) -} diff --git a/apps/explorer-v1/components/Layout/Search.tsx b/apps/explorer-v1/components/Layout/Search.tsx deleted file mode 100644 index 019479133..000000000 --- a/apps/explorer-v1/components/Layout/Search.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { - Button, - ControlGroup, - Search16, - TextField, - triggerToast, -} from '@siafoundation/design-system' -import React from 'react' -import { useFormik } from 'formik' -import { useRouter } from 'next/router' -import { navigatorApi } from '../../config' -import { getHrefForType } from '../../lib/utils' -import { NvgEntityType, NvgEntityTypeInfo } from '../../config/navigatorTypes' - -export function Search() { - const router = useRouter() - - const formik = useFormik({ - initialValues: { - query: '', - }, - onSubmit: async (values, actions) => { - const response = await fetch(`${navigatorApi}/hash/${values.query}`) - const data: [NvgEntityTypeInfo] = await response.json() - - if (!data?.length) { - actions.setErrors({ - query: 'Invalid hash', - }) - triggerToast('Invalid hash.') - return - } - actions.resetForm() - - const master = data[0] - router.push(getHrefForType(master.Type, values.query)) - }, - }) - - return ( -
-
- - - - -
-
- ) -} diff --git a/apps/explorer-v1/components/Layout/index.tsx b/apps/explorer-v1/components/Layout/index.tsx deleted file mode 100644 index 445a8aa3a..000000000 --- a/apps/explorer-v1/components/Layout/index.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { - NavbarSite, - Container, - Heading, - Link, - ScrollArea, - Separator, - SimpleLogoIcon, - Text, - webLinks, - RedditIcon, - LogoDiscord24, - LogoTwitter24, - LogoGithub24, - SitePageHead, - FaucetIcon, - LinkButton, - Tooltip, -} from '@siafoundation/design-system' -import { routes } from '../../config/routes' -import backgroundImage from '../../assets/leaves-background.png' -import previewImage from '../../assets/leaves-preview.png' -import { Search } from './Search' -import { appLink, appName, isMainnet, networkName } from '../../config' -import { NavDropdownMenu } from './NavDropdownMenu' - -type Props = { - title: string - description: string - path: string - children: React.ReactNode -} - -export function Layout({ title, description, path, children }: Props) { - return ( -
- - -
- - - {!isMainnet && ( - - - - - - )} - - -
-
-
-
-
-
-
- {children} -
- - -
-
-
- - - -
- explorer -
- - - Keops - - - x - - - The Sia Foundation - - -
-
- - - - - - - - - - - - -
-
-
-
-
-
- - Ecosystem - - {/* - SiaStats - */} - - Sia Central Host Browser - - - Sia Central Host Troubleshoot - - - Coinmarketcap - -
-
-
-
-
-
-
- -
- ) -} - -type LinkProps = { - children: React.ReactNode - href: string - target?: string -} - -function BottomLink({ children, href, target }: LinkProps) { - return ( - - {children} - - ) -} diff --git a/apps/explorer-v1/components/TxEntityHeader.tsx b/apps/explorer-v1/components/TxEntityHeader.tsx deleted file mode 100644 index 544fcc5cf..000000000 --- a/apps/explorer-v1/components/TxEntityHeader.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Badge, Text, Tooltip } from '@siafoundation/design-system' -import { humanDate, humanNumber } from '@siafoundation/sia-js' -import { useStatus } from '../hooks/useStatus' -import { NvgEntityTx, getNvgEntityTypeLabel } from '../config/navigatorTypes' -import { routes } from '../config/routes' -import { EntityHeading } from './EntityHeading' - -type Props = { - entity: NvgEntityTx -} - -export function TxEntityHeader({ entity }: Props) { - const { data } = entity - const status = useStatus() - const txHash = data[0].MasterHash - const confirmations = data[1]?.Height - ? Math.min(72, status.data?.lastblock - data[1].Height) - : undefined - const height = data[1]?.Height ? humanNumber(data[1].Height) : undefined - const timestamp = data[1]?.Timestamp - ? humanDate(data[1].Timestamp * 1000, { timeStyle: 'short' }) - : undefined - - return ( -
- -
- {!!timestamp && ( - - - {timestamp} - - - )} - {!!height && ( - -
- -
{height}
-
-
|
-
- {confirmations === 72 ? '72+' : confirmations} confirmations -
-
-
- )} -
-
- ) -} diff --git a/apps/explorer-v1/components/TxEntityLayout.tsx b/apps/explorer-v1/components/TxEntityLayout.tsx deleted file mode 100644 index d8bf2ed48..000000000 --- a/apps/explorer-v1/components/TxEntityLayout.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { EntityList, EntityListItemProps } from '@siafoundation/design-system' -import { NvgDatum, DatumProps } from './NvgDatum' -import { NvgEntityTx } from '../config/navigatorTypes' -import { TxEntityHeader } from './TxEntityHeader' -import { ContentLayout } from './ContentLayout' - -type Props = { - entity: NvgEntityTx - values: DatumProps[] - details?: React.ReactNode - inputs: EntityListItemProps[] - outputs: EntityListItemProps[] - relatedOperations?: EntityListItemProps[] - incompleteData?: boolean -} - -export function TxEntityLayout({ - entity, - values, - details, - inputs, - outputs, - relatedOperations, - incompleteData, -}: Props) { - return ( - - - {!!values?.length && ( -
- {values.map((item) => ( - - ))} -
- )} - {details} -
- } - > -
-
-
- -
-
- -
-
- {!!relatedOperations?.length && ( - - )} -
- - ) -} diff --git a/apps/explorer-v1/components/entities/AddressEntity/index.tsx b/apps/explorer-v1/components/entities/AddressEntity/index.tsx deleted file mode 100644 index cd585600c..000000000 --- a/apps/explorer-v1/components/entities/AddressEntity/index.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import { - Badge, - Tabs, - TabsContent, - TabsList, - TabsTrigger, - Tooltip, - ChartTimeValue, - ValueSc, - EntityList, - EntityListItemProps, -} from '@siafoundation/design-system' -import { humanNumber, humanSiacoin } from '@siafoundation/sia-js' -import { NvgDatum, DatumProps } from '../../NvgDatum' -import { - getNvgEntityTypeInitials, - getNvgEntityTypeLabel, - NvgAddressEntity, -} from '../../../config/navigatorTypes' -import { useBalanceHistory } from '../../../hooks/useBalanceHistory' -import { useMemo, useState } from 'react' -import { useStatus } from '../../../hooks/useStatus' -import { routes } from '../../../config/routes' -import { EntityHeading } from '../../EntityHeading' -import { useUtxos } from '../../../hooks/useUtxos' -import { getHrefForType } from '../../../lib/utils' -import BigNumber from 'bignumber.js' -import { ContentLayout } from '../../ContentLayout' - -type Tab = 'transactions' | 'evolution' | 'utxos' - -type Props = { - entity: NvgAddressEntity -} - -export function AddressEntity({ entity }: Props) { - const status = useStatus() - const { data } = entity - - const [tab, setTab] = useState('transactions') - const balanceHistory = useBalanceHistory( - tab === 'evolution' ? entity.data[0].MasterHash : null - ) - const utxos = useUtxos(tab === 'utxos' ? entity.data[0].MasterHash : null) - - const firstSeen = data[1].firstSeen === '-' ? 0 : data[1].firstSeen - const firstSeenAgo = - status.data?.lastblock && status.data.lastblock - firstSeen - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Confirmed SC', - sc: new BigNumber(data[1].balanceSc), - comment: ( -
- - -
- ), - }, - { - label: 'Pending SC', - sc: new BigNumber(data[1].pendingSc), - }, - { - label: 'Confirmed SF', - sf: data[1].balanceSf, - }, - { - label: 'Pending SF', - sf: data[1].pendingSf, - }, - ] - return list - }, [data]) - - const address = data[0].MasterHash - - const transactions = useMemo(() => { - const list: EntityListItemProps[] = [ - ...data[1].unconfirmedTransactions.map((tx) => ({ - hash: tx.TxHash, - sc: new BigNumber(tx.ScValue), - sf: tx.SfValue, - label: getNvgEntityTypeLabel(tx.TxType), - initials: getNvgEntityTypeInitials(tx.TxType), - href: getHrefForType(tx.TxType, tx.TxHash), - timestamp: tx.Timestamp * 1000, - unconfirmed: true, - })), - ...data[1].last100Transactions.map((tx) => ({ - hash: tx.MasterHash, - sc: new BigNumber(tx.ScChange), - sf: tx.SfChange, - label: getNvgEntityTypeLabel(tx.TxType), - initials: getNvgEntityTypeInitials(tx.TxType), - href: getHrefForType(tx.TxType, tx.MasterHash), - height: tx.Height, - timestamp: tx.Timestamp * 1000, - })), - ] - return list - }, [data]) - - return ( - -
- -
- - {`${humanNumber(firstSeen)}`} - - - - {`${humanNumber(data[1].TotalTxCount)}`} transactions - - -
-
-
- {values.map((item) => ( - - ))} -
-
- } - > - setTab(val as Tab)} - > - - Last 100 transactions - Balance evolution - Unspent outputs - - - - - - humanSiacoin(v), - }, - { - name: 'SF', - dataset: balanceHistory.data?.sfJson || [], - formatValue: (v) => humanNumber(v, { units: 'SF' }), - }, - ]} - height={300} - /> - - - ({ - sc: new BigNumber(output.hastings || '0'), - sf: output.sf, - hash: output.output, - label: getNvgEntityTypeLabel('output'), - initials: getNvgEntityTypeInitials('output'), - href: getHrefForType('output', output.output), - }))} - /> - - - - ) -} diff --git a/apps/explorer-v1/components/entities/AllowancePostEntity/index.tsx b/apps/explorer-v1/components/entities/AllowancePostEntity/index.tsx deleted file mode 100644 index 5bde2124d..000000000 --- a/apps/explorer-v1/components/entities/AllowancePostEntity/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useMemo } from 'react' -import { - getEntityTxInputs, - getEntityTxOutputs, - getTotalTransacted, -} from '../../../lib/transaction' -import { NvgAllowancePostEntity } from '../../../config/navigatorTypes' -import { DatumProps } from '../../NvgDatum' -import { TxEntityLayout } from '../../TxEntityLayout' - -type Props = { - entity: NvgAllowancePostEntity -} - -export function AllowancePostEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Total transacted', - sc: getTotalTransacted(entity).sc, - }, - { - label: 'Collateral for contract ID', - entityType: 'contract', - entityValue: data[1].HashSynonyms.split(',')[0], - }, - ] - return list - }, [entity, data]) - - const inputs = useMemo(() => getEntityTxInputs(entity), [entity]) - const outputs = useMemo(() => getEntityTxOutputs(entity), [entity]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/BlockEntity/index.tsx b/apps/explorer-v1/components/entities/BlockEntity/index.tsx deleted file mode 100644 index 14ea83d9b..000000000 --- a/apps/explorer-v1/components/entities/BlockEntity/index.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, - Badge, - Tooltip, - EntityList, - Text, -} from '@siafoundation/design-system' -import { - humanBytes, - humanDifficulty, - humanHashrate, - humanNumber, -} from '@siafoundation/sia-js' -import { NvgDatum, DatumProps } from '../../NvgDatum' -import { - getNvgEntityTypeInitials, - getNvgEntityTypeLabel, - NvgBlockEntity, -} from '../../../config/navigatorTypes' -import { useMemo } from 'react' -import { routes } from '../../../config/routes' -import { EntityHeading } from '../../EntityHeading' -import { getHrefForType } from '../../../lib/utils' -import BigNumber from 'bignumber.js' -import { ContentLayout } from '../../ContentLayout' - -type Props = { - entity: NvgBlockEntity -} - -export function BlockEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Mined by', - value: data[1].MiningPool, - }, - { - label: 'Difficulty', - value: humanDifficulty(data[1].Difficulty), - }, - { - label: 'Hashrate', - value: humanHashrate(data[1].Hashrate), - }, - { - label: 'Block hash', - hash: data[1].Hash, - }, - { - label: 'Miner payout address', - entityType: 'address', - entityValue: data[1].MinerPayoutAddress, - }, - ] - return list - }, [data]) - - const other = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Miner arbitrary data', - hash: data[1].MinerArbitraryData, - }, - { - label: 'Coins in circulation', - sc: new BigNumber(data[1].TotalCoins), - }, - ] - return list - }, [data]) - - const historic = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Historic contracts count', - value: data[1].TotalContractCount.toLocaleString(), - }, - { - label: 'Historic contracts size', - value: humanBytes(data[1].TotalContractSize), - }, - { - label: 'Historic contracts cost', - sc: new BigNumber(data[1].TotalContractCost), - }, - { - label: 'Historic transaction count', - value: data[1].TransactionCount.toLocaleString(), - }, - { - label: 'Historic revisions', - value: data[1].FileContractRevisionCount.toLocaleString(), - }, - { - label: 'Historic storage proofs', - value: data[1].StorageProofCount.toLocaleString(), - }, - ] - return list - }, [data]) - - const active = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Active contract count', - value: data[1].ActiveContractCount.toLocaleString(), - }, - { - label: 'Active contract size', - value: humanBytes(data[1].ActiveContractSize), - }, - { - label: 'Active contract cost', - sc: new BigNumber(data[1].ActiveContractCost), - }, - ] - return list - }, [data]) - - const block = data[0].MasterHash - - return ( - -
- -
- - - {`${humanNumber(data[1].NewContracts)}`} contracts - - - - - {`${humanNumber(data[1].NewTx)}`} transactions - - -
-
-
- {values.map((item) => ( - - ))} -
- - - - - Contracts - - - -
-
- - Active - -
- {active.map((item) => ( - - ))} -
-
-
- - Historic - -
- {historic.map((item) => ( - - ))} -
-
-
-
-
- - - - Other - - - -
- {other.map((item) => ( - - ))} -
-
-
-
- - } - > - ({ - hash: tx.TxHash, - label: getNvgEntityTypeLabel(tx.TxType), - initials: getNvgEntityTypeInitials(tx.TxType), - href: getHrefForType(tx.TxType, tx.TxHash), - sc: new BigNumber(tx.TotalAmountSc), - sf: tx.TotalAmountSf, - }))} - /> -
- ) -} diff --git a/apps/explorer-v1/components/entities/BlockRewardEntity/index.tsx b/apps/explorer-v1/components/entities/BlockRewardEntity/index.tsx deleted file mode 100644 index 46156dad2..000000000 --- a/apps/explorer-v1/components/entities/BlockRewardEntity/index.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useMemo } from 'react' -import { - getEntityTxOutputs, - getTotalTransacted, -} from '../../../lib/transaction' -import { NvgBlockRewardEntity } from '../../../config/navigatorTypes' -import { DatumProps } from '../../NvgDatum' -import { TxEntityLayout } from '../../TxEntityLayout' -import { EntityListItemProps } from '@siafoundation/design-system' -import BigNumber from 'bignumber.js' - -type Props = { - entity: NvgBlockRewardEntity -} - -export function BlockRewardEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Total transacted', - sc: getTotalTransacted(entity).sc, - }, - ] - return list - }, [entity]) - - const inputs = useMemo(() => { - const list: EntityListItemProps[] = [] - let subsidy = 300000 - data[1].Height - if (subsidy < 30000) { - subsidy = 30000 - } // Minimal future subsidy - subsidy = subsidy * 1000000000000000000000000 - - // Adding Foundation subsidy to coinbase, if exists - let foundationSubsidy = 0 - if (data[2].transactions.length == 2) { - foundationSubsidy = data[2].transactions[1].ScChange - } - - const fullSubsidy = subsidy + foundationSubsidy - - list.push({ - label: 'Coinbase', - sc: new BigNumber(-fullSubsidy), - }) - - const minedFees = data[2].transactions[0].ScChange - subsidy - list.push({ - label: 'Collected transaction fees', - sc: new BigNumber(-minedFees), - }) - return list - }, [data]) - const outputs = useMemo(() => getEntityTxOutputs(entity), [entity]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/CollateralPostEntity/index.tsx b/apps/explorer-v1/components/entities/CollateralPostEntity/index.tsx deleted file mode 100644 index a5da3be6d..000000000 --- a/apps/explorer-v1/components/entities/CollateralPostEntity/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useMemo } from 'react' -import { - getEntityTxInputs, - getEntityTxOutputs, - getTotalTransacted, -} from '../../../lib/transaction' -import { NvgCollateralPostEntity } from '../../../config/navigatorTypes' -import { DatumProps } from '../../NvgDatum' -import { TxEntityLayout } from '../../TxEntityLayout' - -type Props = { - entity: NvgCollateralPostEntity -} - -export function CollateralPostEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Total transacted', - sc: getTotalTransacted(entity).sc, - }, - { - label: 'Collateral for contract ID', - entityType: 'contract', - entityValue: data[1].HashSynonyms.split(',')[0], - }, - ] - return list - }, [entity, data]) - - const inputs = useMemo(() => getEntityTxInputs(entity), [entity]) - const outputs = useMemo(() => getEntityTxOutputs(entity), [entity]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/ContractForEntity/index.tsx b/apps/explorer-v1/components/entities/ContractForEntity/index.tsx deleted file mode 100644 index 2c48412a4..000000000 --- a/apps/explorer-v1/components/entities/ContractForEntity/index.tsx +++ /dev/null @@ -1,247 +0,0 @@ -/* eslint-disable react/no-unescaped-entities */ -import { EntityListItemProps, Text } from '@siafoundation/design-system' -import { humanBytes, humanNumber } from '@siafoundation/sia-js' -import { useMemo } from 'react' -import { DatumProps } from '../../NvgDatum' -import { getContractStatus } from '../../../lib/transaction' -import { NvgContractEntity } from '../../../config/navigatorTypes' -import { TxEntityLayout } from '../../TxEntityLayout' -import { ContractConditionsSection } from '../../ContractConditionsSection' -import { getNvgEntityItemProps } from '../../../lib/utils' -import BigNumber from 'bignumber.js' - -type Props = { - entity: NvgContractEntity -} - -export function ContractForEntity({ entity }: Props) { - const { data } = entity - - const incompleteData = !Object.keys(data[1]).length - - const values = useMemo(() => { - if (incompleteData) return [] - const sfFees = new BigNumber(data[1].SfFees) - const feesPercentage = sfFees - .dividedBy( - new BigNumber( - data[1].ValidProof1Value + - data[1].ValidProof2Value + - data[1].Fees - - data[1].HostValue - ).plus(sfFees) - ) - .multipliedBy(100) - .toFixed(2) // This avoid some errors on contracts formed by `us` - - const list: DatumProps[] = [ - { - label: 'Status', - value: getContractStatus(entity), - }, - { - label: 'New / Renewal', - value: data[1].Renew ? 'renewal' : 'new', - }, - { - label: 'File size at contract formation', - value: humanBytes(data[1].OriginalFileSize), - }, - { - label: 'File size at last revision', - value: humanBytes(data[1].CurrentFileSize), - }, - { - label: 'Last revision number', - value: humanNumber(data[1].RevisionNum), - }, - { - label: 'Window for proof of storage', - value: - humanNumber(data[1].WindowStart) + - ' - ' + - humanNumber(data[1].WindowEnd), - }, - { - label: 'Total SC', - sc: new BigNumber(data[1].RenterValue + data[1].HostValue), - }, - { - label: 'Fees paid to miners', - sc: new BigNumber(data[1].Fees), - }, - { - label: 'Fees paid to SFs', - sc: new BigNumber(data[1].SfFees), - comment: `${feesPercentage}% of the renter's allowance`, - }, - ] - - console.log(data) - if (data[6]?.Height >= 0) { - list.push({ - label: 'Renewed into Contract ID', - entityType: 'contract', - entityValue: data[6].ContractId, - }) - } - - return list - }, [entity, data, incompleteData]) - - const details = useMemo(() => { - if (incompleteData) { - return ( -
- - Error, the explorer ran into an issue collecting data about this - contract. - -
- ) - } - return - }, [entity, incompleteData]) - - const inputs = useMemo(() => { - if (incompleteData) return [] - const list: EntityListItemProps[] = [ - getNvgEntityItemProps('allowancePost', { - // label: 'Renter: allowance posting hash', - hash: data[1].AllowancePosting, - sc: new BigNumber(-data[1].RenterValue), - }), - ] - - if (data[1].Renter2Value > 0) { - // Tri-input contracts - list.push( - getNvgEntityItemProps('allowancePost', { - // label: 'Renter: allowance posting hash', - hash: data[1].Allowance2Posting, - sc: new BigNumber(-data[1].Renter2Value), - }) - ) - } - - if (data[1].Renter3Value > 0) { - // Quad-input contracts - list.push( - getNvgEntityItemProps('allowancePost', { - // label: 'Renter: allowance posting hash', - hash: data[1].Allowance3Posting, - sc: new BigNumber(-data[1].Renter3Value), - }) - ) - } - - list.push( - getNvgEntityItemProps('collateralPost', { - // label: 'Host: collateral posting hash', - hash: data[1].CollateralPosting, - sc: new BigNumber(-data[1].HostValue), - }) - ) - - return list - }, [data, incompleteData]) - - const outputs = useMemo(() => { - if (incompleteData) { - return [] - } - const list: EntityListItemProps[] = [ - getNvgEntityItemProps('contract', { - label: 'Formed contract ID', - hash: data[1].ContractId, - sc: new BigNumber(data[1].ValidProof1Value + data[1].ValidProof2Value), - }), - ] - - // Exceptional contracts where some amount returns to the renter address in the contract formation - if (data[5]?.transactions.length > 0) { - for (let i = 0; i < data[5].transactions.length; i++) { - list.push( - getNvgEntityItemProps('address', { - label: 'Renter address', - hash: data[5].transactions[i].Address, - sc: new BigNumber(data[5].transactions[i].ScChange), - }) - ) - } - } - - // SiaFund fees - list.push({ - label: 'Fees paid to SFs', - sc: new BigNumber(data[1].SfFees), - }) - - // Miner fees - list.push({ - label: 'Fees paid to miners', - sc: new BigNumber(data[1].Fees), - }) - - return list - }, [data, incompleteData]) - - const operations = useMemo(() => { - if (incompleteData) { - return [] - } - const list: EntityListItemProps[] = [] - if (data[2]?.Height >= 0) { - // Only if there is a Revision - list.push( - getNvgEntityItemProps('revision', { - hash: data[2].MasterHash, - height: data[2].Height, - timestamp: data[2].Timestamp * 1000, - }) - ) - } - if (data[4]?.Height >= 0) { - // Only if there is an Storage Proof - list.push( - getNvgEntityItemProps('storageproof', { - hash: data[4].MasterHash, - height: data[4].Height, - timestamp: data[4].Timestamp * 1000, - }) - ) - } - if (data[6]?.Height >= 0) { - // Only if there is an atomic renewal - list.push( - getNvgEntityItemProps('contractrenewal', { - hash: data[6].ContractId, - height: data[6].Height, - timestamp: data[6].Timestamp * 1000, - }) - ) - } - if (data[3]?.Height >= 0) { - // Only if there is a Resolution - list.push( - getNvgEntityItemProps('contractresol', { - hash: data[3].MasterHash, - height: data[3].Height, - timestamp: data[3].Timestamp * 1000, - }) - ) - } - return list - }, [data, incompleteData]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/ContractResEntity/index.tsx b/apps/explorer-v1/components/entities/ContractResEntity/index.tsx deleted file mode 100644 index fd2d9bc2e..000000000 --- a/apps/explorer-v1/components/entities/ContractResEntity/index.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { useMemo } from 'react' -import { getContractStatus, getTotalTransacted } from '../../../lib/transaction' -import { NvgContractResEntity } from '../../../config/navigatorTypes' -import { DatumProps } from '../../NvgDatum' -import { TxEntityLayout } from '../../TxEntityLayout' -import { EntityListItemProps } from '@siafoundation/design-system' -import { getNvgEntityItemProps } from '../../../lib/utils' -import BigNumber from 'bignumber.js' - -type Props = { - entity: NvgContractResEntity -} - -export function ContractResEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Total transacted', - sc: getTotalTransacted(entity).sc, - }, - { - label: 'Status', - value: getContractStatus(entity), - }, - { - label: 'Contract ID', - entityType: 'contract', - entityValue: data[1].ContractId, - }, - ] - return list - }, [entity, data]) - - const inputs = useMemo(() => { - const list: EntityListItemProps[] = [ - getNvgEntityItemProps('contract', { - // label: 'File Contract ID', - hash: data[1].ContractId, - sc: new BigNumber( - -(data[1].Output0Value + data[1].Output1Value + data[1].Output2Value) - ), - }), - ] - return list - }, [data]) - - const outputs = useMemo(() => { - const list: EntityListItemProps[] = [] - if (data[1].Result == 'fail') { - list.push( - getNvgEntityItemProps('address', { - label: 'Renter address: returned allowance', - hash: data[1].Output0Address, - sc: new BigNumber(data[1].Output0Value), - }) - ) - list.push( - getNvgEntityItemProps('address', { - label: 'Host address: unused collateral', - hash: data[1].Output1Address, - sc: new BigNumber(data[1].Output1Value), - }) - ) - } else { - list.push( - getNvgEntityItemProps('address', { - label: 'Renter address: unused allowance', - hash: data[1].Output0Address, - sc: new BigNumber(data[1].Output0Value), - }) - ) - list.push( - getNvgEntityItemProps('address', { - label: 'Host address: payout + collateral back', - hash: data[1].Output1Address, - sc: new BigNumber(data[1].Output1Value), - }) - ) - } - if (data[1].Result == 'fail') { - list.push( - getNvgEntityItemProps('address', { - label: 'Burning address: lost collateral', - hash: data[1].Output2Address, - sc: new BigNumber(data[1].Output2Value), - }) - ) - } - return list - }, [data]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/ContractRevEntity/index.tsx b/apps/explorer-v1/components/entities/ContractRevEntity/index.tsx deleted file mode 100644 index 0e30168c6..000000000 --- a/apps/explorer-v1/components/entities/ContractRevEntity/index.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useMemo } from 'react' -import { DatumProps } from '../../NvgDatum' -import { - getEntityTxInputs, - getEntityTxOutputs, - getTotalTransacted, -} from '../../../lib/transaction' -import { NvgRevisionEntity } from '../../../config/navigatorTypes' -import { TxEntityLayout } from '../../TxEntityLayout' -import { ContractConditionsSection } from '../../ContractConditionsSection' - -type Props = { - entity: NvgRevisionEntity -} - -export function ContractRevEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Total transacted', - sc: getTotalTransacted(entity).sc, - }, - { - label: 'New revision number', - value: data[1].NewRevisionNum, - }, - { - label: 'New file size', - value: data[1].NewFileSize, - }, - { - label: 'Contract ID', - entityType: 'contract', - entityValue: data[1].ContractId, - }, - ] - return list - }, [entity, data]) - - const details = useMemo(() => { - return - }, [entity]) - - const inputs = useMemo(() => { - return getEntityTxInputs(entity) - }, [entity]) - - const outputs = useMemo(() => { - return getEntityTxOutputs(entity) - }, [entity]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/HostAnnEntity/index.tsx b/apps/explorer-v1/components/entities/HostAnnEntity/index.tsx deleted file mode 100644 index 0cd4aa66d..000000000 --- a/apps/explorer-v1/components/entities/HostAnnEntity/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useMemo } from 'react' -import { - getEntityTxInputs, - getEntityTxOutputs, - getTotalTransacted, -} from '../../../lib/transaction' -import { NvgHostAnnEntity } from '../../../config/navigatorTypes' -import { DatumProps } from '../../NvgDatum' -import { TxEntityLayout } from '../../TxEntityLayout' -import { ValueCopyable } from '@siafoundation/design-system' - -type Props = { - entity: NvgHostAnnEntity -} - -export function HostAnnEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Total transacted', - sc: getTotalTransacted(entity).sc, - }, - { - label: 'Announced IP', - value: ( - - ), - }, - ] - return list - }, [entity, data]) - - const inputs = useMemo(() => getEntityTxInputs(entity), [entity]) - const outputs = useMemo(() => getEntityTxOutputs(entity), [entity]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/OutputEntity/index.tsx b/apps/explorer-v1/components/entities/OutputEntity/index.tsx deleted file mode 100644 index deb5b5934..000000000 --- a/apps/explorer-v1/components/entities/OutputEntity/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useMemo } from 'react' -import { NvgOutputEntity } from '../../../config/navigatorTypes' -import { Badge } from '@siafoundation/design-system' -import { NvgDatum, DatumProps } from '../../NvgDatum' -import { EntityHeading } from '../../EntityHeading' -import { routes } from '../../../config/routes' -import BigNumber from 'bignumber.js' -import { ContentLayout } from '../../ContentLayout' - -type Props = { - entity: NvgOutputEntity -} - -export function OutputEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Belongs to address', - entityType: 'address', - entityValue: data[1].Address, - }, - { - label: 'Created on block', - entityType: 'block', - entityValue: String(data[1].CreatedOnBlock || ''), - }, - ] - - // if (data[1].SpentOnBlock) { - list.push({ - label: 'Spent on block', - entityType: 'block', - entityValue: String(data[1].SpentOnBlock || ''), - }) - // } - - if (data[1].ScValue) { - list.push({ - label: 'SC Value', - sc: new BigNumber(data[1].ScValue), - }) - } - if (data[1].SfValue) { - list.push({ - label: 'SF Value', - sf: data[1].SfValue, - }) - } - - return list - }, [data]) - - const output = data[0].MasterHash - - return ( - -
- -
- - {data[1].Spent ? 'Spent' : 'Unspent'} - -
-
-
- {values.map((item) => ( - - ))} -
- - } - /> - ) -} diff --git a/apps/explorer-v1/components/entities/OutputEntitySkeleton/index.tsx b/apps/explorer-v1/components/entities/OutputEntitySkeleton/index.tsx deleted file mode 100644 index 0aa22a601..000000000 --- a/apps/explorer-v1/components/entities/OutputEntitySkeleton/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Skeleton, DatumSkeleton } from '@siafoundation/design-system' -import { times } from 'lodash' -import { ContentLayout } from '../../ContentLayout' - -export function OutputEntitySkeleton() { - return ( - -
- - -
-
- {times(4, (i) => ( - - ))} -
- - } - /> - ) -} diff --git a/apps/explorer-v1/components/entities/ScTxEntity/index.tsx b/apps/explorer-v1/components/entities/ScTxEntity/index.tsx deleted file mode 100644 index 308d96b82..000000000 --- a/apps/explorer-v1/components/entities/ScTxEntity/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useMemo } from 'react' -import { NvgScTxEntity } from '../../../config/navigatorTypes' -import { TxEntityLayout } from '../../TxEntityLayout' -import { - getEntityTxInputs, - getEntityTxOutputs, - getTotalTransacted, -} from '../../../lib/transaction' -import BigNumber from 'bignumber.js' - -type Props = { - entity: NvgScTxEntity -} - -export function ScTxEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const totals = getTotalTransacted(entity) - - return [ - { - label: 'Transacted SC', - sc: totals.sc, - }, - { - label: 'Transacted SF', - sf: totals.sf, - }, - { - label: 'Fees', - sc: new BigNumber(data[1].Fees), - }, - ] - }, [entity, data]) - - const inputs = useMemo(() => getEntityTxInputs(entity), [entity]) - const outputs = useMemo(() => getEntityTxOutputs(entity), [entity]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/SfTxEntity/index.tsx b/apps/explorer-v1/components/entities/SfTxEntity/index.tsx deleted file mode 100644 index 16f50ac6a..000000000 --- a/apps/explorer-v1/components/entities/SfTxEntity/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useMemo } from 'react' -import { NvgSfTxEntity } from '../../../config/navigatorTypes' -import { TxEntityLayout } from '../../TxEntityLayout' -import { - getEntityTxInputs, - getEntityTxOutputs, - getTotalTransacted, -} from '../../../lib/transaction' -import BigNumber from 'bignumber.js' - -type Props = { - entity: NvgSfTxEntity -} - -export function SfTxEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const totals = getTotalTransacted(entity) - - return [ - { - label: 'Transacted SC', - sc: totals.sc, - }, - { - label: 'Transacted SF', - sf: totals.sf, - }, - { - label: 'Fees', - sc: new BigNumber(data[1].Fees), - }, - ] - }, [entity, data]) - - const inputs = useMemo(() => getEntityTxInputs(entity), [entity]) - const outputs = useMemo(() => getEntityTxOutputs(entity), [entity]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/StorageProofEntity/index.tsx b/apps/explorer-v1/components/entities/StorageProofEntity/index.tsx deleted file mode 100644 index 95fe32fb8..000000000 --- a/apps/explorer-v1/components/entities/StorageProofEntity/index.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useMemo } from 'react' -import { - getEntityTxInputs, - getEntityTxOutputs, - getTotalTransacted, -} from '../../../lib/transaction' -import { NvgStorageProofEntity } from '../../../config/navigatorTypes' -import { DatumProps } from '../../NvgDatum' -import { TxEntityLayout } from '../../TxEntityLayout' - -type Props = { - entity: NvgStorageProofEntity -} - -export function StorageProofEntity({ entity }: Props) { - const { data } = entity - - const values = useMemo(() => { - const list: DatumProps[] = [ - { - label: 'Total transacted', - sc: getTotalTransacted(entity).sc, - }, - { - label: 'Proof for contract ID', - entityType: 'contract', - entityValue: data[1].ContractId, - }, - ] - return list - }, [entity, data]) - - const inputs = useMemo(() => getEntityTxInputs(entity), [entity]) - const outputs = useMemo(() => getEntityTxOutputs(entity), [entity]) - - return ( - - ) -} diff --git a/apps/explorer-v1/components/entities/TxEntity404/index.tsx b/apps/explorer-v1/components/entities/TxEntity404/index.tsx deleted file mode 100644 index fd58ec697..000000000 --- a/apps/explorer-v1/components/entities/TxEntity404/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Entity, nvgEntityTypes } from '../../../config/navigatorTypes' -import { Entity404 } from '../Entity404' - -type Props = { - entity: Entity -} - -export function TxEntity404({ entity }: Props) { - if (!nvgEntityTypes.includes(entity.type)) { - return ( - - ) - } - return ( - - ) -} diff --git a/apps/explorer-v1/config/navigatorTypes.ts b/apps/explorer-v1/config/navigatorTypes.ts deleted file mode 100644 index ab56cb927..000000000 --- a/apps/explorer-v1/config/navigatorTypes.ts +++ /dev/null @@ -1,480 +0,0 @@ -export type NvgEntityType = - | 'ScTx' - | 'SfTx' - | 'contract' - | 'contractform' - | 'contractrenewal' - | 'block' - | 'contractresol' - | 'storageproof' - | 'revision' - | 'host ann' - | 'blockreward' - | 'allowancePost' - | 'collateralPost' - | 'output' - | 'address' - | 'SfClaim' - | 'foundationsub' - | 'unconfirmed' - -export const nvgEntityTypes = [ - 'ScTx', - 'SfTx', - 'contract', - 'contractform', - 'contractrenewal', - 'block', - 'contractresol', - 'storageproof', - 'revision', - 'host ann', - 'blockreward', - 'allowancePost', - 'collateralPost', - 'output', - 'address', - 'SfClaim', - 'foundationsub', - 'unconfirmed', -] - -const nvgEntityTypeMap: Record = { - block: 'block', - ScTx: 'siacoin transfer', - SfTx: 'siafund transfer', - contract: 'contract formation', - contractform: 'contract formation', - contractrenewal: 'contract renewal', - contractresol: 'contract resolution', - storageproof: 'storage proof', - revision: 'revision', - 'host ann': 'host announcement', - blockreward: 'block reward', - allowancePost: 'allowance post', - collateralPost: 'collateral post', - output: 'output', - address: 'address', - SfClaim: 'siafund claim', - foundationsub: 'foundation subsidy', - unconfirmed: 'unconfirmed', -} - -const nvgEntityTypeInitialsMap: Record = { - block: 'Bk', - ScTx: 'Tx', - SfTx: 'Tx', - contract: 'Tx', - contractform: 'Tx', - contractrenewal: 'Tx', - contractresol: 'Tx', - storageproof: 'Tx', - revision: 'Tx', - 'host ann': 'Tx', - blockreward: 'Tx', - allowancePost: 'Tx', - collateralPost: 'Tx', - output: 'O', - address: 'A', - SfClaim: 'Tx', - foundationsub: 'Tx', - unconfirmed: 'Tx', -} - -export function getNvgEntityTypeLabel(txType: NvgEntityType): string { - return nvgEntityTypeMap[txType] -} - -export function getNvgEntityTypeInitials(txType: NvgEntityType): string { - return nvgEntityTypeInitialsMap[txType] -} - -export type NvgEntityTypeInfo = { - Type: T - MasterHash: string -} - -export type NvgBlockInfo = { - Height: number - Timestamp: number - TransactionCount: number - Hash: string - MinerPayoutAddress: string - MinerArbitraryData: string - Difficulty: number - Hashrate: number - TotalCoins: number - SiacoinInputCount: number - SiacoinOutputCount: number - FileContractRevisionCount: number - StorageProofCount: number - SiafundInputCount: number - SiafundOutputCount: number - ActiveContractCost: number - ActiveContractCount: number - ActiveContractSize: number - TotalContractCost: number - TotalContractCount: number - TotalContractSize: number - NewContracts: number - NewTx: number - MiningPool: string - FeeCount: number - FeeCountHastings: number -} - -type NvgContractInfo = { - MasterHash: string - ContractId: string - AllowancePosting: string - RenterValue: number - Allowance2Posting: string | 'null' - Renter2Value: number - Allowance3Posting: string | 'null' - Renter3Value: number - CollateralPosting: string - HostValue: number - Fees: number - WindowStart: number - WindowEnd: number - RevisionNum: number - OriginalFileSize: number - CurrentFileSize: number - ValidProof1Output: string - ValidProof1Address: string - ValidProof1Value: number - ValidProof2Output: string - ValidProof2Address: string - ValidProof2Value: number - MissedProof1Output: string - MissedProof1Address: string - MissedProof1Value: number - MissedProof2Output: string - MissedProof2Address: string - MissedProof2Value: number - MissedProof3Output: string - MissedProof3Address: string - MissedProof3Value: number - Height: number - Timestamp: number - Status: string - Renew: number - AtomicRenewal: number - RenewsContractId?: string - SfFees: number -} - -type NvgRevisionInfo = { - MasterHash: string - ContractId: string - Fees: number - NewRevisionNum: number - NewFileSize: number - ValidProof1Address: string - ValidProof1Value: number - ValidProof2Address: string - ValidProof2Value: number - MissedProof1Address: string - MissedProof1Value: number - MissedProof2Address: string - MissedProof2Value: number - MissedProof3Address: string - MissedProof3Value: number - Height: number - Timestamp: number - HashSynonyms: string -} - -type NvgContractResolution = { - MasterHash: string - ContractId: string - Fees: number - Result: string - Height: number - Timestamp: number - Output0Address: string - Output0Value: number - Output1Address: string - Output1Value: number - Output2Address: string - Output2Value: number -} - -type NvgStorageProofInfo = { - MasterHash: string - ContractId: string - HashSynonyms: string - Height: number - Timestamp: number - Fees: number -} - -export type NvgAddressChange = { - MasterHash: string - Address: string - ScChange: number - SfChange: number - Height: number - Timestamp: number - TxType: NvgEntityType -} - -type NvgUnconfirmedBalance = { - Address: string - TxHash: string - Timestamp: number - ScValue: number - SfValue: number - TxType: NvgEntityType -} - -type NvgHostAnnInfo = { - TxHash: string - HashSynonyms: string - Height: number - Timestamp: number - Fees: number - IP: string -} - -type NvgTxInfo = { - TxHash: string - HashSynonyms: string - Height: number - Timestamp: number - Fees: number -} - -export type BlockTransaction = { - Height: number - TxHash: string - TxType: NvgEntityType - TotalAmountSc: number - TotalAmountSf: number -} - -type NvgAddressInfo = { - balanceSc: number - receivedSc: number - sentSc: number - balanceSf: number - TotalTxCount: number - firstSeen: number | '-' - last100Transactions: Pick< - NvgAddressChange, - 'MasterHash' | 'ScChange' | 'SfChange' | 'Height' | 'Timestamp' | 'TxType' - >[] - pendingSc: number - pendingSf: number - unconfirmedTransactions: Pick< - NvgUnconfirmedBalance, - 'ScValue' | 'SfValue' | 'TxType' | 'Timestamp' | 'TxHash' - >[] -} - -type NvgOutputInfo = { - OutputId: string - ScValue: number - SfValue: number - Address: string - CreatedOnBlock: number - Spent: number - SpentOnBlock: number - FoundationUnclaimed: number -} - -export type NvgAddressEntity = { - type: 'address' - data: [NvgEntityTypeInfo<'address'>, NvgAddressInfo] -} - -export type NvgOutputEntity = { - type: 'output' - data: [NvgEntityTypeInfo<'output'>, NvgOutputInfo] -} - -export type NvgBlockEntity = { - type: 'block' - data: [ - NvgEntityTypeInfo<'block'>, - NvgBlockInfo, - { - transactions: Pick< - BlockTransaction, - 'TxHash' | 'TxType' | 'TotalAmountSc' | 'TotalAmountSf' - >[] - } - ] -} - -export type NvgSimpleTxEntity = { - type: T - data: [ - NvgEntityTypeInfo, - Pick, - { - transactions: Pick< - NvgAddressChange, - 'Address' | 'ScChange' | 'SfChange' | 'TxType' - >[] - } - ] -} -export type NvgScTxEntity = NvgSimpleTxEntity<'ScTx'> -export type NvgSfTxEntity = NvgSimpleTxEntity<'SfTx'> -export type NvgBlockRewardEntity = NvgSimpleTxEntity<'blockreward'> -export type NvgAllowancePostEntity = NvgSimpleTxEntity<'allowancePost'> -export type NvgCollateralPostEntity = NvgSimpleTxEntity<'collateralPost'> - -export type NvgHostAnnEntity = { - type: 'host ann' - data: [ - // 0: type - NvgEntityTypeInfo<'host ann'>, - // 1: hostanninfo - ( - | Record - | Pick< - NvgHostAnnInfo, - 'HashSynonyms' | 'Height' | 'Timestamp' | 'Fees' | 'IP' - > - ), - { - transactions: Pick< - NvgAddressChange, - 'Address' | 'ScChange' | 'SfChange' - >[] - } - ] -} - -export type NvgContractEntity = { - type: 'contract' - data: [ - // 0: type - NvgEntityTypeInfo<'contract'>, - // 1: contract - NvgContractInfo, - // 2: revision - Record | NvgRevisionInfo, - // 3: resolution - Record | NvgContractResolution, - // 4: storage proof - Record | NvgStorageProofInfo, - // 5: transactions - { transactions: Pick[] }, - // 6: atomic renewal - Record | NvgContractInfo - ] -} - -export type NvgContractResEntity = { - type: 'contractresol' - data: [ - // 0: type - NvgEntityTypeInfo<'contractresol'>, - // 1: contractresolutions - Record | NvgContractResolution, - { - transactions: Pick< - NvgAddressChange, - 'Address' | 'ScChange' | 'SfChange' - >[] - } - ] -} - -export type NvgRevisionEntity = { - type: 'revision' - data: [ - // 0: type - NvgEntityTypeInfo<'revision'>, - // 1: revisioninfo - Record | NvgRevisionInfo, - { - transactions: Pick< - NvgAddressChange, - 'Address' | 'ScChange' | 'SfChange' - >[] - } - ] -} - -export type NvgStorageProofEntity = { - type: 'storageproof' - data: [ - // 0: type - NvgEntityTypeInfo<'storageproof'>, - // 1: storageproofsinfo - Record | NvgRevisionInfo, - { - transactions: Pick< - NvgAddressChange, - 'Address' | 'ScChange' | 'SfChange' - >[] - } - ] -} - -export type NvgUnconfirmedEntity = { - type: 'unconfirmed' - data: [ - // 0: type - NvgEntityTypeInfo<'unconfirmed'> - ] -} - -export type NvgErrorEntity = { - type: 'error' - data: [] -} - -export type Entity = - | NvgAddressEntity - | NvgOutputEntity - | NvgBlockEntity - | NvgScTxEntity - | NvgSfTxEntity - | NvgBlockRewardEntity - | NvgAllowancePostEntity - | NvgCollateralPostEntity - | NvgHostAnnEntity - | NvgContractEntity - | NvgContractResEntity - | NvgRevisionEntity - | NvgStorageProofEntity - | NvgUnconfirmedEntity - | NvgErrorEntity - -export type NvgEntityTx = - | NvgScTxEntity - | NvgSfTxEntity - | NvgBlockRewardEntity - | NvgAllowancePostEntity - | NvgCollateralPostEntity - | NvgHostAnnEntity - | NvgContractEntity - | NvgContractResEntity - | NvgRevisionEntity - | NvgStorageProofEntity - -// Tx entity types that have a transactions list in the 2 index of their data array -export type NvgEntityTxns2Index = - | NvgScTxEntity - | NvgSfTxEntity - | NvgBlockRewardEntity - | NvgAllowancePostEntity - | NvgCollateralPostEntity - | NvgHostAnnEntity - // | ContractEntity - | NvgContractResEntity - | NvgRevisionEntity - | NvgStorageProofEntity - -export type NvgAddressUtxo = { - hastings?: string - output: string - sf?: number -} - -export type NvgAddressUtxos = NvgAddressUtxo[] diff --git a/apps/explorer-v1/config/routes.ts b/apps/explorer-v1/config/routes.ts deleted file mode 100644 index eaf75c92c..000000000 --- a/apps/explorer-v1/config/routes.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const routes = { - home: { - index: '/', - }, - tx: { - // index: '/txs', - view: '/tx/[id]', - }, - block: { - // index: '/blocks', - view: '/block/[id]', - }, - address: { - // index: '/addresses', - view: '/address/[id]', - }, - output: { - view: '/output/[id]', - }, - faucet: { - index: '/faucet', - }, -} - -export function getRouteHref(viewRoute: string, id: string) { - return viewRoute.replace('[id]', id) -} diff --git a/apps/explorer-v1/hooks/useBalanceHistory.ts b/apps/explorer-v1/hooks/useBalanceHistory.ts deleted file mode 100644 index facc9e02f..000000000 --- a/apps/explorer-v1/hooks/useBalanceHistory.ts +++ /dev/null @@ -1,56 +0,0 @@ -import useSWR from 'swr' -import axios from 'axios' -import { navigatorApi } from '../config' - -const url = `${navigatorApi}/balance-track` - -function getBalanceHistoryKey(address?: string) { - return address ? `${url}/${address}` : null -} - -type Point = { - timestamp: number - value: number -} - -export type NavigatorBalanceHistory = { - scDataBool: boolean - scJson: Point[] - scUsdJson: Point[] - sfDataBool: boolean - sfJson: Point[] - sfUsdJson: Point[] - status: string -} - -export function useBalanceHistory(address?: string) { - return useSWR( - getBalanceHistoryKey(address), - async () => { - const response = await axios(url, { - method: 'post', - data: { - addresses: [address], - currency: 'USD', - }, - }) - return { - ...response.data, - scJson: formatPoints(response.data.scJson), - scUsdJson: formatPoints(response.data.scUsdJson), - sfJson: formatPoints(response.data.sfJson), - sfUsdJson: formatPoints(response.data.sfUsdJson), - } - }, - { - dedupingInterval: 60_000 * 5, - } - ) -} - -function formatPoints(points: [number, number][]): Point[] { - return points.map(([timestamp, balance]) => ({ - timestamp, - value: balance, - })) -} diff --git a/apps/explorer-v1/hooks/useDebugToggle.tsx b/apps/explorer-v1/hooks/useDebugToggle.tsx deleted file mode 100644 index 51f73c9b9..000000000 --- a/apps/explorer-v1/hooks/useDebugToggle.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useEffect, useState } from 'react' - -let i = null - -export function useDebugToggle() { - const [toggle, setToggle] = useState(false) - useEffect(() => { - i = setInterval(() => setToggle((t) => !t), 2000) - return () => { - clearInterval(i) - } - }, []) - - return toggle -} diff --git a/apps/explorer-v1/hooks/useEntity.ts b/apps/explorer-v1/hooks/useEntity.ts deleted file mode 100644 index 50bcbc64a..000000000 --- a/apps/explorer-v1/hooks/useEntity.ts +++ /dev/null @@ -1,30 +0,0 @@ -import useSWR from 'swr' -import { navigatorApi } from '../config' -import { Entity } from '../config/navigatorTypes' - -const url = `${navigatorApi}/hash/` - -export function getEntityKey(hash: string) { - return url + hash -} - -export async function fetchEntity(hash: string): Promise { - const response = await fetch(getEntityKey(hash)) - const data = await response.json() - if (data?.length) { - return { - type: data[0].Type, - data, - } - } - return { - type: 'error', - data: [], - } -} - -export function useEntity(hash: string) { - return useSWR(getEntityKey(hash), () => fetchEntity(hash), { - dedupingInterval: 30_000, - }) -} diff --git a/apps/explorer-v1/hooks/useLanding.ts b/apps/explorer-v1/hooks/useLanding.ts deleted file mode 100644 index b376685d2..000000000 --- a/apps/explorer-v1/hooks/useLanding.ts +++ /dev/null @@ -1,24 +0,0 @@ -import useSWR from 'swr' -import { navigatorApi } from '../config' -import { NvgBlockInfo, BlockTransaction } from '../config/navigatorTypes' - -export const landingKey = `${navigatorApi}/landing` - -type NavigatorLanding = { - last10ScTx: Pick[] - last10Contracts: Pick[] - last10Others: Pick[] - last10Blocks: Pick[] -} - -export async function fetchLanding(): Promise { - const response = await fetch(landingKey) - return response.json() -} - -export function useLanding() { - return useSWR(landingKey, () => fetchLanding(), { - refreshInterval: 30_000, - dedupingInterval: 30_000, - }) -} diff --git a/apps/explorer-v1/hooks/useStatus.ts b/apps/explorer-v1/hooks/useStatus.ts deleted file mode 100644 index b3e657c05..000000000 --- a/apps/explorer-v1/hooks/useStatus.ts +++ /dev/null @@ -1,28 +0,0 @@ -import useSWR from 'swr' -import { navigatorApi } from '../config' - -export const statusKey = `${navigatorApi}/status` - -type NavigatorStatus = { - coinsupply: number - consensusblock: number - heartbeat: number - lastblock: number - mempool: number - peers: number - totalTx: number - version: string -} - -export async function fetchStatus(): Promise { - const response = await fetch(statusKey) - const x = await response.json() - return x[0] -} - -export function useStatus() { - return useSWR(statusKey, () => fetchStatus(), { - refreshInterval: 30_000, - dedupingInterval: 30_000, - }) -} diff --git a/apps/explorer-v1/hooks/useUtxos.ts b/apps/explorer-v1/hooks/useUtxos.ts deleted file mode 100644 index 837525126..000000000 --- a/apps/explorer-v1/hooks/useUtxos.ts +++ /dev/null @@ -1,23 +0,0 @@ -import useSWR from 'swr' -import { navigatorApi } from '../config' -import { NvgAddressUtxos } from '../config/navigatorTypes' - -function getUtxosKey(address?: string) { - return address ? `${navigatorApi}/unspent_outputs/${address}` : null -} - -async function fetchUtxos(address: string) { - const response = await fetch(getUtxosKey(address)) - const data = await response.json() - return data -} - -export function useUtxos(address?: string) { - return useSWR( - getUtxosKey(address), - () => fetchUtxos(address), - { - dedupingInterval: 60_000, - } - ) -} diff --git a/apps/explorer-v1/lib/transaction.ts b/apps/explorer-v1/lib/transaction.ts deleted file mode 100644 index e514ca838..000000000 --- a/apps/explorer-v1/lib/transaction.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { EntityListItemProps } from '@siafoundation/design-system' -import { - NvgContractEntity, - Entity, - NvgEntityTxns2Index, - NvgEntityType, - NvgRevisionEntity, - getNvgEntityTypeInitials, - getNvgEntityTypeLabel, -} from '../config/navigatorTypes' -import { getHrefForType } from './utils' -import BigNumber from 'bignumber.js' - -// Adapted from https://github.com/hakkane84/navigator-sia/blob/3f6ae63a48426c8810f0bd11de55a4b69b736200/web/nav_assets/navigator_web.js#L1571 -// commented out unreachable cases -export function getContractStatus(tx: Entity): string { - if (!['contractresol', 'contract'].includes(tx.type)) { - return 'err' - } - if (tx.type === 'contract') { - const { data } = tx - // if (data[1].Result === 'fail' || data[1].Status === 'complete-fail') { - if (data[1].Status === 'complete-fail') { - // if (data[1].MissedProof3Value === 0 || data[1].Output2Value === '0') { - if (data[1].MissedProof3Value === 0) { - return 'Unused' - } else { - return 'Failed' - } - } else if ( - // data[1].Result === 'success' || - data[1].Status === 'complete-succ' - ) { - if ( - // data[1].MissedProof2Value === data[1].ValidProof2Value || data[0].Type === 'contractresol' - data[1].MissedProof2Value === data[1].ValidProof2Value - ) { - return 'Unused' - } else { - return 'Successful' - } - } else if (data[1].Status === 'ongoing') { - return 'Ongoing' - } - } else if (tx.type === 'contractresol') { - const { data } = tx - // Contract resolutions - if (data[1].Result === 'success') { - return 'Successful' - } else { - return 'Failed' - } - } else { - return 'err' - } -} - -export function getTotalTransacted(entity: NvgEntityTxns2Index) { - const { data } = entity - let sc = new BigNumber(0) - let sf = 0 - for (let n = 0; n < data[2].transactions.length; n++) { - if (data[2].transactions[n].ScChange > 0) { - sc = sc.plus(data[2].transactions[n].ScChange) - } else if (data[2].transactions[n].SfChange > 0) { - sf = sf + data[2].transactions[n].SfChange - } - } - if (data[1].Fees) { - sc = sc.plus(data[1].Fees) - } - return { - sc, - sf, - } -} - -export function getContractConditions({ - data, -}: NvgContractEntity | NvgRevisionEntity) { - return { - // Conditions upon success - success: { - returnedAllowance: data[1].ValidProof1Value, - payoutCollateral: data[1].ValidProof2Value, - }, - // Conditions upon fail - fail: { - returnedAllowance: data[1].MissedProof1Value, - returnedCollateral: data[1].MissedProof2Value, - burntCollateral: data[1].MissedProof3Value, - }, - } -} - -export function getEntityTxInputs({ type, data }: NvgEntityTxns2Index) { - const inputs: EntityListItemProps[] = [] - - for (let n = 0; n < data[2].transactions.length; n++) { - const hash = data[2].transactions[n].Address - - // Identifying senders - if ( - data[2].transactions[n].ScChange < 0 || - data[2].transactions[n].SfChange < 0 - ) { - let sc = new BigNumber(0) - let sf = 0 - let label = getNvgEntityTypeLabel('address') - - if (data[2].transactions[n].ScChange < 0) { - sc = new BigNumber(data[2].transactions[n].ScChange) // Push the change in SC - } else if (data[2].transactions[n].SfChange < 0) { - sf = data[2].transactions[n].SfChange // Push the change in SF - } - - // Naming the sending object - if (type == 'ScTx' || type == 'SfTx') { - label = 'sender address' - } else if (type == 'host ann') { - label = 'host address' - } else if (type == 'allowancePost') { - label = 'renter address' - } else if (type == 'collateralPost' || type == 'storageproof') { - label = 'host address' - } else if (type == 'revision') { - label = 'address' - } - - inputs.push({ - label, - initials: getNvgEntityTypeInitials('address'), - href: getHrefForType('address', hash), - sc, - sf, - hash, - }) - } - } - - return inputs -} - -export function getEntityTxOutputs({ - type: entityType, - data, -}: NvgEntityTxns2Index) { - const outputs: EntityListItemProps[] = [] - - for (let n = 0; n < data[2].transactions.length; n++) { - let hash = data[2].transactions[n].Address - - // In contracts, instead of the destination address I show the final contract ID - if ( - (entityType == 'allowancePost' || entityType == 'collateralPost') && - data[2].transactions[n].TxType == 'contractform' - ) { - hash = data[1].HashSynonyms - } - - // Identifying receivers - if ( - data[2].transactions[n].ScChange > 0 || - data[2].transactions[n].SfChange > 0 - ) { - let label = getNvgEntityTypeLabel('address') - let type = 'address' as NvgEntityType - let sc = new BigNumber(0) - let sf = 0 - if (data[2].transactions[n].ScChange > 0) { - sc = new BigNumber(data[2].transactions[n].ScChange) // Push the change in SC - } else if (data[2].transactions[n].SfChange > 0) { - sf = data[2].transactions[n].SfChange // Push the change in SF - } - - if (entityType == 'ScTx') { - label = 'receiver address' - type = 'address' - } else if ( - entityType == 'SfTx' && - data[2].transactions[n].TxType != 'SfClaim' && - data[2].transactions[n].SfChange > 0 - ) { - label = 'receiver address' - type = 'address' - } else if ( - entityType == 'SfTx' && - data[2].transactions[n].TxType != 'SfClaim' && - data[2].transactions[n].ScChange > 0 - ) { - label = 'sender wallet return (unspent output)' - type = 'address' - } else if ( - entityType == 'SfTx' && - data[2].transactions[n].TxType == 'SfClaim' - ) { - type = 'address' - label = 'SiaFund dividend claim address (sender)' - } else if (entityType == 'host ann') { - label = 'host address' - type = 'address' - } else if ( - entityType == 'allowancePost' && - data[2].transactions[n].TxType != 'contractform' - ) { - type = 'address' - label = 'renter address' - } else if ( - entityType == 'allowancePost' && - data[2].transactions[n].TxType == 'contractform' - ) { - type = 'contract' - label = 'allowance for contract ID' - } else if ( - entityType == 'collateralPost' && - data[2].transactions[n].TxType != 'contractform' - ) { - type = 'address' - label = 'host address' - } else if ( - entityType == 'collateralPost' && - data[2].transactions[n].TxType == 'contractform' - ) { - type = 'contract' - label = 'collateral for contract ID' - } else if (entityType == 'storageproof') { - type = 'address' - label = 'host address' - } else if ( - entityType == 'blockreward' && - data[2].transactions[n].TxType == 'blockreward' - ) { - type = 'address' - label = 'miner payout address' - } else if ( - entityType == 'blockreward' && - data[2].transactions[n].TxType == 'foundationsub' - ) { - type = 'address' - label = 'Sia Foundation address' - } else if (entityType == 'revision') { - type = 'address' - label = 'address (same wallet)' - } - - outputs.push({ - label, - initials: getNvgEntityTypeInitials(type), - href: getHrefForType(type, hash), - sc, - sf, - hash, - }) - } - } - - if (data[1].Fees) { - outputs.push({ - label: 'miner fees', - sc: new BigNumber(data[1].Fees), - }) - } - - return outputs -} diff --git a/apps/explorer-v1/lib/utils.ts b/apps/explorer-v1/lib/utils.ts deleted file mode 100644 index 8d383ad7c..000000000 --- a/apps/explorer-v1/lib/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { routes } from '../config/routes' -import { - getNvgEntityTypeInitials, - getNvgEntityTypeLabel, - NvgEntityType, - nvgEntityTypes, -} from '../config/navigatorTypes' -import { EntityListItemProps } from '@siafoundation/design-system' - -const linkableTypes = nvgEntityTypes - -export function getHrefForType(type: string, value: string) { - if (linkableTypes.includes(type)) { - const route = routes[type] || routes.tx - return route.view.replace('[id]', value) - } else { - return undefined - } -} - -export function getNvgEntityItemProps( - type: NvgEntityType, - props: EntityListItemProps -): EntityListItemProps { - return { - label: getNvgEntityTypeLabel(type), - initials: getNvgEntityTypeInitials(type), - href: getHrefForType(type, props.hash), - avatarShape: type === 'address' || type === 'block' ? 'square' : 'circle', - blockHref: props.height - ? getHrefForType('block', String(props.height)) - : undefined, - ...props, - } -} diff --git a/apps/explorer-v1/package.json b/apps/explorer-v1/package.json deleted file mode 100644 index f8c79a72b..000000000 --- a/apps/explorer-v1/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "explorer-v1", - "description": "The `explorer-v1` user interface, a Sia blockchain explorer interface for Navigator.", - "version": "0.6.0", - "private": true, - "license": "MIT" -} diff --git a/apps/explorer-v1/pages/_app.tsx b/apps/explorer-v1/pages/_app.tsx deleted file mode 100644 index 093c8c706..000000000 --- a/apps/explorer-v1/pages/_app.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import '../config/style.css' -import { NextAppSsr } from '@siafoundation/design-system' - -export default NextAppSsr diff --git a/apps/explorer-v1/pages/_document.tsx b/apps/explorer-v1/pages/_document.tsx deleted file mode 100644 index 4476fd33a..000000000 --- a/apps/explorer-v1/pages/_document.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { NextDocument } from '@siafoundation/design-system' - -export default NextDocument diff --git a/apps/explorer-v1/pages/address/[id].tsx b/apps/explorer-v1/pages/address/[id].tsx deleted file mode 100644 index 8a16b596b..000000000 --- a/apps/explorer-v1/pages/address/[id].tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { fetchEntity, getEntityKey, useEntity } from '../../hooks/useEntity' -import { useRouter } from 'next/router' -import { AddressEntity } from '../../components/entities/AddressEntity' -import { AddressEntitySkeleton } from '../../components/entities/AddressEntitySkeleton' -import { Entity404 } from '../../components/entities/Entity404' -import { routes } from '../../config/routes' -import { Layout } from '../../components/Layout' -import { getTitleId } from '@siafoundation/design-system' - -export default function ViewAddress() { - const router = useRouter() - const id = (router.query.id || '') as string - const entity = useEntity(id) - - const title = getTitleId('Address', id, 6) - const description = getTitleId('View details for address', id, 6) - const path = routes.address.view.replace('[id]', id) - - if (entity.data?.type === 'address') { - return ( - - - - ) - } - - if (entity.data?.type === 'error') { - return ( - - - - ) - } - - return ( - - - - ) -} - -export async function getServerSideProps({ params }) { - try { - const id: string = params.id - const entity = await fetchEntity(id) - - return { - props: { - fallback: { - [getEntityKey(id)]: entity, - }, - }, - } - } catch (e) { - return { - props: {}, - } - } -} diff --git a/apps/explorer-v1/pages/block/[id].tsx b/apps/explorer-v1/pages/block/[id].tsx deleted file mode 100644 index b4555b17e..000000000 --- a/apps/explorer-v1/pages/block/[id].tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { fetchEntity, getEntityKey, useEntity } from '../../hooks/useEntity' -import { useRouter } from 'next/router' -import { BlockEntity } from '../../components/entities/BlockEntity' -import { BlockEntitySkeleton } from '../../components/entities/BlockEntitySkeleton' -import { Entity404 } from '../../components/entities/Entity404' -import { routes } from '../../config/routes' -import { Layout } from '../../components/Layout' -import { humanNumber } from '@siafoundation/sia-js' -import { getTitleId } from '@siafoundation/design-system' - -export default function ViewBlock() { - const router = useRouter() - const id = (router.query.id || '') as string - const entity = useEntity(id) - - let block = id - if (!isNaN(Number(id))) { - block = humanNumber(id) - } - - const title = getTitleId('Block', block) - const description = getTitleId('View details for block', block) - const path = routes.block.view.replace('[id]', id) - - if (entity.data?.type === 'block') { - return ( - - - - ) - } - - if (entity.data?.type === 'error') { - return ( - - - - ) - } - - return ( - - - - ) -} - -export async function getServerSideProps({ params }) { - try { - const id: string = params.id - const entity = await fetchEntity(id) - - return { - props: { - fallback: { - [getEntityKey(id)]: entity, - }, - }, - } - } catch (e) { - return { - props: {}, - } - } -} diff --git a/apps/explorer-v1/pages/faucet.tsx b/apps/explorer-v1/pages/faucet.tsx deleted file mode 100644 index 564211801..000000000 --- a/apps/explorer-v1/pages/faucet.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { webLinks } from '@siafoundation/design-system' -import { useEffect } from 'react' -import { Faucet } from '../components/Faucet' -import { isMainnet } from '../config' - -export default function FaucetPage() { - useEffect(() => { - if (isMainnet) { - window.location.replace(webLinks.explore.testnetFaucet) - } - }, []) - - return -} diff --git a/apps/explorer-v1/pages/index.tsx b/apps/explorer-v1/pages/index.tsx deleted file mode 100644 index 0624345e4..000000000 --- a/apps/explorer-v1/pages/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Home } from '../components/Home' -import { Layout } from '../components/Layout' -import { routes } from '../config/routes' -import { fetchEntity, getEntityKey } from '../hooks/useEntity' -import { fetchLanding, landingKey } from '../hooks/useLanding' -import { fetchStatus, statusKey } from '../hooks/useStatus' - -export default function HomePage() { - const title = 'Home' - const description = 'Sia blockchain explorer' - const path = routes.home.index - - return ( - - - - ) -} - -export async function getServerSideProps() { - try { - const [status, landing] = await Promise.all([fetchStatus(), fetchLanding()]) - const height = String(status.lastblock) - const block = await fetchEntity(String(height)) - - return { - props: { - fallback: { - [statusKey]: status, - [landingKey]: landing, - [getEntityKey(height)]: block, - }, - }, - } - } catch (e) { - return { - props: {}, - } - } -} diff --git a/apps/explorer-v1/pages/output/[id].tsx b/apps/explorer-v1/pages/output/[id].tsx deleted file mode 100644 index 637eb9249..000000000 --- a/apps/explorer-v1/pages/output/[id].tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { fetchEntity, getEntityKey, useEntity } from '../../hooks/useEntity' -import { useRouter } from 'next/router' -import { OutputEntity } from '../../components/entities/OutputEntity' -import { OutputEntitySkeleton } from '../../components/entities/OutputEntitySkeleton' -import { Entity404 } from '../../components/entities/Entity404' -import { Layout } from '../../components/Layout' -import { routes } from '../../config/routes' -import { getTitleId } from '@siafoundation/design-system' - -export default function ViewOutput() { - const router = useRouter() - const id = (router.query.id || '') as string - const entity = useEntity(id) - - const title = getTitleId('Output', id, 6) - const description = getTitleId('View details for output', id, 6) - const path = routes.output.view.replace('[id]', id) - - if (entity.data?.type === 'output') { - return ( - - - - ) - } - - if (entity.data?.type === 'error') { - return ( - - - - ) - } - - return ( - - - - ) -} - -export async function getServerSideProps({ params }) { - try { - const id: string = params.id - const entity = await fetchEntity(id) - - return { - props: { - fallback: { - [getEntityKey(id)]: entity, - }, - }, - } - } catch (e) { - return { - props: {}, - } - } -} diff --git a/apps/explorer-v1/pages/tx/[id].tsx b/apps/explorer-v1/pages/tx/[id].tsx deleted file mode 100644 index 1e60ee169..000000000 --- a/apps/explorer-v1/pages/tx/[id].tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { fetchEntity, getEntityKey, useEntity } from '../../hooks/useEntity' -import { useRouter } from 'next/router' -import { ScTxEntity } from '../../components/entities/ScTxEntity' -import { ContractForEntity } from '../../components/entities/ContractForEntity' -import { ContractResEntity } from '../../components/entities/ContractResEntity' -import { ContractRevEntity } from '../../components/entities/ContractRevEntity' -import { StorageProofEntity } from '../../components/entities/StorageProofEntity' -import { CollateralPostEntity } from '../../components/entities/CollateralPostEntity' -import { AllowancePostEntity } from '../../components/entities/AllowancePostEntity' -import { HostAnnEntity } from '../../components/entities/HostAnnEntity' -import { BlockRewardEntity } from '../../components/entities/BlockRewardEntity' -import { SfTxEntity } from '../../components/entities/SfTxEntity' -import { TxEntitySkeleton } from '../../components/entities/TxEntitySkeleton' -import { TxEntity404 } from '../../components/entities/TxEntity404' -import { routes } from '../../config/routes' -import { getHrefForType } from '../../lib/utils' -import { Layout } from '../../components/Layout' -import { getTitleId } from '@siafoundation/design-system' - -const typeToComponent = { - contract: ContractForEntity, - contractresol: ContractResEntity, - revision: ContractRevEntity, - storageproof: StorageProofEntity, - collateralPost: CollateralPostEntity, - allowancePost: AllowancePostEntity, - 'host ann': HostAnnEntity, - blockreward: BlockRewardEntity, - ScTx: ScTxEntity, - SfTx: SfTxEntity, -} - -export default function ViewTx() { - const router = useRouter() - const id = (router.query.id || '') as string - const tx = useEntity(id) - - const title = getTitleId('Transaction', id, 6) - const description = getTitleId('View details for transaction', id, 6) - const path = routes.tx.view.replace('[id]', id) - - // Sometimes things in transaction list like 'allowancePost' actually point to an address, - // to silenty recover from the error we can redirect to the correct entity view. - // All non tx entity types will return a route, otherwise this will be undefined. - const nonTxRoute = routes[tx.data?.type] - if (nonTxRoute) { - const href = getHrefForType(tx.data?.type, id) - router.replace(href) - } - - if (tx.data) { - const EntityComponent = typeToComponent[tx.data?.type] || TxEntity404 - - return ( - - - - ) - } - - return ( - - - - ) -} - -export async function getServerSideProps({ params }) { - try { - const id: string = params.id - const entity = await fetchEntity(id) - - return { - props: { - fallback: { - [getEntityKey(id)]: entity, - }, - }, - } - } catch (e) { - return { - props: {}, - } - } -} diff --git a/apps/explorer-v1/public/android-chrome-192x192.png b/apps/explorer-v1/public/android-chrome-192x192.png deleted file mode 100644 index fcad2e6a2..000000000 Binary files a/apps/explorer-v1/public/android-chrome-192x192.png and /dev/null differ diff --git a/apps/explorer-v1/public/browserconfig.xml b/apps/explorer-v1/public/browserconfig.xml deleted file mode 100644 index f9c2e67fe..000000000 --- a/apps/explorer-v1/public/browserconfig.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - #2b5797 - - - diff --git a/apps/explorer-v1/public/built-with-sia.png b/apps/explorer-v1/public/built-with-sia.png deleted file mode 100644 index 283b37458..000000000 Binary files a/apps/explorer-v1/public/built-with-sia.png and /dev/null differ diff --git a/apps/explorer-v1/public/favicon-16x16.png b/apps/explorer-v1/public/favicon-16x16.png deleted file mode 100644 index a31b73b5f..000000000 Binary files a/apps/explorer-v1/public/favicon-16x16.png and /dev/null differ diff --git a/apps/explorer-v1/public/favicon-32x32.png b/apps/explorer-v1/public/favicon-32x32.png deleted file mode 100644 index 07497af79..000000000 Binary files a/apps/explorer-v1/public/favicon-32x32.png and /dev/null differ diff --git a/apps/explorer-v1/public/logo-green.png b/apps/explorer-v1/public/logo-green.png deleted file mode 100644 index fcad2e6a2..000000000 Binary files a/apps/explorer-v1/public/logo-green.png and /dev/null differ diff --git a/apps/explorer-v1/public/logo-simple.svg b/apps/explorer-v1/public/logo-simple.svg deleted file mode 100644 index 2052c11a0..000000000 --- a/apps/explorer-v1/public/logo-simple.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/apps/explorer-v1/public/manifest.json b/apps/explorer-v1/public/manifest.json deleted file mode 100644 index b3f58ee3f..000000000 --- a/apps/explorer-v1/public/manifest.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "short_name": "Sia", - "name": "Sia - Explorer", - "description": "Explorer", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#ffffff", - "background_color": "#ffffff" -} diff --git a/apps/explorer-v1/public/mstile-144x144.png b/apps/explorer-v1/public/mstile-144x144.png deleted file mode 100644 index ffad76525..000000000 Binary files a/apps/explorer-v1/public/mstile-144x144.png and /dev/null differ diff --git a/apps/explorer-v1/public/mstile-150x150.png b/apps/explorer-v1/public/mstile-150x150.png deleted file mode 100644 index e075e3c90..000000000 Binary files a/apps/explorer-v1/public/mstile-150x150.png and /dev/null differ diff --git a/apps/explorer-v1/public/mstile-310x150.png b/apps/explorer-v1/public/mstile-310x150.png deleted file mode 100644 index fabd63442..000000000 Binary files a/apps/explorer-v1/public/mstile-310x150.png and /dev/null differ diff --git a/apps/explorer-v1/public/mstile-310x310.png b/apps/explorer-v1/public/mstile-310x310.png deleted file mode 100644 index 3b21ce538..000000000 Binary files a/apps/explorer-v1/public/mstile-310x310.png and /dev/null differ diff --git a/apps/explorer-v1/public/mstile-70x70.png b/apps/explorer-v1/public/mstile-70x70.png deleted file mode 100644 index 932d71fe1..000000000 Binary files a/apps/explorer-v1/public/mstile-70x70.png and /dev/null differ diff --git a/apps/explorer-v1/public/safari-pinned-tab.svg b/apps/explorer-v1/public/safari-pinned-tab.svg deleted file mode 100644 index c531fb25b..000000000 --- a/apps/explorer-v1/public/safari-pinned-tab.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - -Created by potrace 1.14, written by Peter Selinger 2001-2017 - - - - - - - diff --git a/apps/explorer-v1/public/site.webmanifest b/apps/explorer-v1/public/site.webmanifest deleted file mode 100644 index ba9d97afb..000000000 --- a/apps/explorer-v1/public/site.webmanifest +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "Sia - Explorer", - "short_name": "Sia", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" -} diff --git a/apps/explorer-v1/public/texture.mp4 b/apps/explorer-v1/public/texture.mp4 deleted file mode 100644 index 55dcbcf5d..000000000 Binary files a/apps/explorer-v1/public/texture.mp4 and /dev/null differ diff --git a/apps/explorer-v1/public/texture.webm b/apps/explorer-v1/public/texture.webm deleted file mode 100644 index 9e17853bb..000000000 Binary files a/apps/explorer-v1/public/texture.webm and /dev/null differ diff --git a/apps/explorer-v1/public/wordmark.svg b/apps/explorer-v1/public/wordmark.svg deleted file mode 100644 index 83f34935b..000000000 --- a/apps/explorer-v1/public/wordmark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/apps/explorer-v1/tsconfig.json b/apps/explorer-v1/tsconfig.json deleted file mode 100644 index cadb8cda7..000000000 --- a/apps/explorer-v1/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "jsx": "preserve", - "allowJs": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": false, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "resolveJsonModule": true, - "isolatedModules": true, - "incremental": true, - "types": ["jest", "node"] - }, - "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"], - "exclude": ["node_modules", "jest.config.ts"] -} diff --git a/apps/explorer-v1/.eslintrc.json b/apps/explorer/.eslintrc.json similarity index 92% rename from apps/explorer-v1/.eslintrc.json rename to apps/explorer/.eslintrc.json index a4cdc4a25..00ed66c8e 100644 --- a/apps/explorer-v1/.eslintrc.json +++ b/apps/explorer/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": [ - "plugin:@nrwl/nx/react-typescript", + "plugin:@nx/react-typescript", "../../.eslintrc.json", "next", "next/core-web-vitals" diff --git a/apps/explorer-v1/CHANGELOG.md b/apps/explorer/CHANGELOG.md similarity index 98% rename from apps/explorer-v1/CHANGELOG.md rename to apps/explorer/CHANGELOG.md index 2ecd46325..d255a42f7 100644 --- a/apps/explorer-v1/CHANGELOG.md +++ b/apps/explorer/CHANGELOG.md @@ -1,4 +1,4 @@ -# explorer-v1 +# explorer ## 0.6.0 diff --git a/apps/explorer/app/address/[id]/loading.tsx b/apps/explorer/app/address/[id]/loading.tsx new file mode 100644 index 000000000..a09fdffe2 --- /dev/null +++ b/apps/explorer/app/address/[id]/loading.tsx @@ -0,0 +1,5 @@ +import { AddressSkeleton } from '../../../components/AddressSkeleton' + +export default function Loading() { + return +} diff --git a/apps/explorer/app/address/[id]/opengraph-image.tsx b/apps/explorer/app/address/[id]/opengraph-image.tsx new file mode 100644 index 000000000..9d9b301d2 --- /dev/null +++ b/apps/explorer/app/address/[id]/opengraph-image.tsx @@ -0,0 +1,52 @@ +import { getSiaCentralAddress } from '@siafoundation/sia-central' +import { humanSiacoin, humanSiafund } from '@siafoundation/sia-js' +import { getOGImage } from '../../../components/OGImageEntity' +import { siaCentralApi } from '../../../config' +import { truncate } from '@siafoundation/design-system' + +export const revalidate = 60 + +export const alt = 'Address' +export const size = { + width: 1200, + height: 630, +} + +export const contentType = 'image/png' + +export default async function Image({ params }) { + const id = params?.id as string + const address = await getSiaCentralAddress({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }) + + const values = [ + { + label: 'siacoin balance', + value: humanSiacoin(address?.unspent_siacoins || 0), + }, + ] + + if (address?.unspent_siafunds !== '0') { + values.push({ + label: 'siafund balance', + value: humanSiafund(Number(address?.unspent_siafunds) || 0), + }) + } + + return getOGImage( + { + id, + title: truncate(id, 48), + subtitle: 'address', + initials: 'A', + values, + }, + size + ) +} diff --git a/apps/explorer/app/address/[id]/page.tsx b/apps/explorer/app/address/[id]/page.tsx new file mode 100644 index 000000000..3b3a0072f --- /dev/null +++ b/apps/explorer/app/address/[id]/page.tsx @@ -0,0 +1,39 @@ +import { getSiaCentralAddress } from '@siafoundation/sia-central' +import { Address } from '../../../components/Address' +import { Metadata } from 'next' +import { routes } from '../../../config/routes' +import { buildMetadata } from '../../../lib/utils' +import { siaCentralApi } from '../../../config' +import { notFound } from 'next/navigation' + +export function generateMetadata({ params }): Metadata { + const id = decodeURIComponent((params?.id as string) || '') + const title = `Address ${id}` + const description = `View details for address ${id}` + const url = routes.address.view.replace(':id', id) + return buildMetadata({ + title, + description, + url, + }) +} + +export const revalidate = 60 + +export default async function Page({ params }) { + const id = params?.id as string + const a = await getSiaCentralAddress({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }) + + if (a.unspent_siacoins == undefined) { + return notFound() + } + + return
+} diff --git a/apps/explorer-v1/public/apple-touch-icon.png b/apps/explorer/app/apple-icon.png similarity index 100% rename from apps/explorer-v1/public/apple-touch-icon.png rename to apps/explorer/app/apple-icon.png diff --git a/apps/explorer/app/block/[id]/error.tsx b/apps/explorer/app/block/[id]/error.tsx new file mode 100644 index 000000000..a50c7c359 --- /dev/null +++ b/apps/explorer/app/block/[id]/error.tsx @@ -0,0 +1,9 @@ +'use client' + +import { StateError } from '../../../components/StateError' + +export default function Page() { + return ( + + ) +} diff --git a/apps/explorer/app/block/[id]/loading.tsx b/apps/explorer/app/block/[id]/loading.tsx new file mode 100644 index 000000000..8ec3bfeb5 --- /dev/null +++ b/apps/explorer/app/block/[id]/loading.tsx @@ -0,0 +1,5 @@ +import { BlockSkeleton } from '../../../components/BlockSkeleton' + +export default function Loading() { + return +} diff --git a/apps/explorer/app/block/[id]/opengraph-image.tsx b/apps/explorer/app/block/[id]/opengraph-image.tsx new file mode 100644 index 000000000..eef989138 --- /dev/null +++ b/apps/explorer/app/block/[id]/opengraph-image.tsx @@ -0,0 +1,51 @@ +import { getSiaCentralBlock } from '@siafoundation/sia-central' +import { humanDate } from '@siafoundation/sia-js' +import { getOGImage } from '../../../components/OGImageEntity' +import { siaCentralApi } from '../../../config' + +export const revalidate = 60 + +export const alt = 'Block' +export const size = { + width: 1200, + height: 630, +} + +export const contentType = 'image/png' + +export default async function Image({ params }) { + const id = params?.id as string + const b = await getSiaCentralBlock({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }) + + const values = [ + { + label: 'transactions', + value: (b.block.transactions?.length || 0).toLocaleString(), + }, + { + label: 'time', + value: humanDate(b.block.timestamp, { + dateStyle: 'medium', + timeStyle: 'short', + }), + }, + ] + + return getOGImage( + { + id, + title: b.block.height.toLocaleString(), + subtitle: 'block', + initials: 'B', + values, + }, + size + ) +} diff --git a/apps/explorer/app/block/[id]/page.tsx b/apps/explorer/app/block/[id]/page.tsx new file mode 100644 index 000000000..ec84df18b --- /dev/null +++ b/apps/explorer/app/block/[id]/page.tsx @@ -0,0 +1,51 @@ +import { getTitleId } from '@siafoundation/design-system' +import { Block } from '../../../components/Block' +import { routes } from '../../../config/routes' +import { Metadata } from 'next' +import { getSiaCentralBlock } from '@siafoundation/sia-central' +import { buildMetadata } from '../../../lib/utils' +import { siaCentralApi } from '../../../config' +import { notFound } from 'next/navigation' + +export function generateMetadata({ params }): Metadata { + const id = decodeURIComponent((params?.id as string) || '') + const height = Number(id || 0) as number + if (isNaN(height)) { + const title = getTitleId('Block', id) + const description = getTitleId('View details for block', id) + const url = routes.block.view.replace(':id', id) + return buildMetadata({ + title, + description, + url, + }) + } + const title = `Block ${height.toLocaleString()}` + const description = `View details for block ${height.toLocaleString()}` + const url = routes.block.view.replace(':id', id) + return buildMetadata({ + title, + description, + url, + }) +} + +export const revalidate = 60 + +export default async function Page({ params }) { + const id = params?.id as string + const b = await getSiaCentralBlock({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }) + + if (!b.block) { + return notFound() + } + + return +} diff --git a/apps/explorer/app/contract/[id]/error.tsx b/apps/explorer/app/contract/[id]/error.tsx new file mode 100644 index 000000000..4c2af5b1d --- /dev/null +++ b/apps/explorer/app/contract/[id]/error.tsx @@ -0,0 +1,9 @@ +'use client' + +import { StateError } from '../../../components/StateError' + +export default function Page() { + return ( + + ) +} diff --git a/apps/explorer/app/contract/[id]/loading.tsx b/apps/explorer/app/contract/[id]/loading.tsx new file mode 100644 index 000000000..bfa7a7b20 --- /dev/null +++ b/apps/explorer/app/contract/[id]/loading.tsx @@ -0,0 +1,5 @@ +import { ContractSkeleton } from '../../../components/ContractSkeleton' + +export default function Loading() { + return +} diff --git a/apps/explorer/app/contract/[id]/opengraph-image.tsx b/apps/explorer/app/contract/[id]/opengraph-image.tsx new file mode 100644 index 000000000..a1c575eac --- /dev/null +++ b/apps/explorer/app/contract/[id]/opengraph-image.tsx @@ -0,0 +1,76 @@ +import { + getSiaCentralContract, + getSiaCentralExchangeRates, +} from '@siafoundation/sia-central' +import { humanBytes, humanDate } from '@siafoundation/sia-js' +import { getOGImage } from '../../../components/OGImageEntity' +import { siaCentralApi } from '../../../config' +import { truncate } from '@siafoundation/design-system' +import { lowerCase } from 'lodash' +import { siacoinToDollars } from '../../../lib/currency' + +export const revalidate = 60 + +export const alt = 'Contract' +export const size = { + width: 1200, + height: 630, +} + +export const contentType = 'image/png' + +export default async function Image({ params }) { + const id = params?.id as string + + const [c, r] = await Promise.all([ + getSiaCentralContract({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }), + getSiaCentralExchangeRates({ + config: { + api: siaCentralApi, + }, + }), + ]) + + const values = [ + { + label: 'data size', + value: humanBytes(c.contract.file_size), + }, + { + label: 'expiration', + value: humanDate(c.contract.expiration_timestamp, { + dateStyle: 'short', + timeStyle: 'short', + }), + }, + { + label: 'payout', + value: siacoinToDollars(c.contract.payout, r.rates), + }, + ] + + return getOGImage( + { + id, + title: truncate(c.contract.id, 30), + subtitle: 'contract', + status: lowerCase(c.contract.status), + statusColor: + c.contract.status === 'obligationSucceeded' + ? 'green' + : c.contract.status === 'obligationFailed' + ? 'red' + : 'amber', + initials: 'C', + values, + }, + size + ) +} diff --git a/apps/explorer/app/contract/[id]/page.tsx b/apps/explorer/app/contract/[id]/page.tsx new file mode 100644 index 000000000..cfc1b5b3d --- /dev/null +++ b/apps/explorer/app/contract/[id]/page.tsx @@ -0,0 +1,97 @@ +import { + SiaCentralContract, + getSiaCentralContract, + getSiaCentralExchangeRates, + getSiaCentralTransaction, +} from '@siafoundation/sia-central' +import { ContractView } from '../../../components/ContractView' +import { Metadata } from 'next' +import { routes } from '../../../config/routes' +import { buildMetadata } from '../../../lib/utils' +import { siaCentralApi } from '../../../config' +import { notFound } from 'next/navigation' + +export function generateMetadata({ params }): Metadata { + const id = decodeURIComponent((params?.id as string) || '') + const title = `Contract ${id}` + const description = `View details for contact ${id}` + const url = routes.contract.view.replace(':id', id) + return buildMetadata({ + title, + description, + url, + }) +} + +export const revalidate = 60 + +export default async function Page({ params }) { + const id = params?.id as string + const [c, r] = await Promise.all([ + getSiaCentralContract({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }), + getSiaCentralExchangeRates({ + config: { + api: siaCentralApi, + }, + }), + ]) + + const contract = c?.contract + const formationTxnId = getFormationTxnId(contract) + const finalRevisionTxnId = contract?.transaction_id || '' + + const [ft, rt] = await Promise.all([ + getSiaCentralTransaction({ + params: { + id: formationTxnId, + }, + config: { + api: siaCentralApi, + }, + }), + getSiaCentralTransaction({ + params: { + id: finalRevisionTxnId, + }, + config: { + api: siaCentralApi, + }, + }), + ]) + + const formationTransaction = ft?.transaction + const renewedFrom = formationTransaction?.contract_revisions?.[0] + const renewalTransaction = rt?.transaction + const renewedTo = renewalTransaction?.storage_contracts?.[0] + + if (!c.contract) { + return notFound() + } + + return ( + + ) +} + +function getFormationTxnId(contract: SiaCentralContract) { + let id = contract?.transaction_id + if (contract?.previous_revisions?.length) { + id = + contract.previous_revisions[contract.previous_revisions?.length - 1] + .transaction_id + } + return id +} diff --git a/apps/explorer/app/faucet/error.tsx b/apps/explorer/app/faucet/error.tsx new file mode 100644 index 000000000..c4387e4f0 --- /dev/null +++ b/apps/explorer/app/faucet/error.tsx @@ -0,0 +1,7 @@ +'use client' + +import { StateError } from '../../components/StateError' + +export default function Page() { + return +} diff --git a/apps/explorer/app/faucet/loading.tsx b/apps/explorer/app/faucet/loading.tsx new file mode 100644 index 000000000..3f5dae891 --- /dev/null +++ b/apps/explorer/app/faucet/loading.tsx @@ -0,0 +1,5 @@ +import { FaucetSkeleton } from '../../components/FaucetSkeleton' + +export default function Loading() { + return +} diff --git a/apps/explorer/app/faucet/opengraph-image.tsx b/apps/explorer/app/faucet/opengraph-image.tsx new file mode 100644 index 000000000..81b31f2c2 --- /dev/null +++ b/apps/explorer/app/faucet/opengraph-image.tsx @@ -0,0 +1,19 @@ +import { getOGImage } from '../../components/OGImage' + +export const alt = 'Faucet' +export const size = { + width: 1200, + height: 630, +} + +export const contentType = 'image/png' + +export default async function Image() { + return getOGImage( + { + title: 'Zen Faucet', + subtitle: 'Zen testnet siacoin faucet.', + }, + size + ) +} diff --git a/apps/explorer/app/faucet/page.tsx b/apps/explorer/app/faucet/page.tsx new file mode 100644 index 000000000..3104d171f --- /dev/null +++ b/apps/explorer/app/faucet/page.tsx @@ -0,0 +1,19 @@ +import { Metadata } from 'next' +import { Faucet } from '../../components/Faucet' +import { routes } from '../../config/routes' +import { buildMetadata } from '../../lib/utils' + +export function generateMetadata(): Metadata { + const title = 'Faucet' + const description = 'Sia testnet faucet' + const url = routes.faucet.index + return buildMetadata({ + title, + description, + url, + }) +} + +export default function Page() { + return +} diff --git a/apps/explorer-v1/public/favicon.ico b/apps/explorer/app/favicon.ico similarity index 100% rename from apps/explorer-v1/public/favicon.ico rename to apps/explorer/app/favicon.ico diff --git a/apps/explorer/app/host/[id]/error.tsx b/apps/explorer/app/host/[id]/error.tsx new file mode 100644 index 000000000..6fc6b8300 --- /dev/null +++ b/apps/explorer/app/host/[id]/error.tsx @@ -0,0 +1,7 @@ +'use client' + +import { StateError } from '../../../components/StateError' + +export default function Page() { + return +} diff --git a/apps/explorer/app/host/[id]/loading.tsx b/apps/explorer/app/host/[id]/loading.tsx new file mode 100644 index 000000000..7a946abfc --- /dev/null +++ b/apps/explorer/app/host/[id]/loading.tsx @@ -0,0 +1,5 @@ +import { HostSkeleton } from '../../../components/HostSkeleton' + +export default function Loading() { + return +} diff --git a/apps/explorer/app/host/[id]/opengraph-image.tsx b/apps/explorer/app/host/[id]/opengraph-image.tsx new file mode 100644 index 000000000..4e315f7c4 --- /dev/null +++ b/apps/explorer/app/host/[id]/opengraph-image.tsx @@ -0,0 +1,67 @@ +import { + getSiaCentralExchangeRates, + getSiaCentralHost, +} from '@siafoundation/sia-central' +import { getOGImage } from '../../../components/OGImageEntity' +import { siaCentralApi } from '../../../config' +import { + getDownloadCost, + getStorageCost, + getUploadCost, +} from '../../../lib/host' + +export const revalidate = 60 + +export const alt = 'Host' +export const size = { + width: 1200, + height: 630, +} + +export const contentType = 'image/png' + +export default async function Image({ params }) { + const id = params?.id as string + const [h, r] = await Promise.all([ + getSiaCentralHost({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }), + getSiaCentralExchangeRates({ + config: { + api: siaCentralApi, + }, + }), + ]) + + const values = [ + { + label: 'storage', + value: getStorageCost({ host: h.host, rates: r.rates }), + }, + { + label: 'download', + value: getDownloadCost({ host: h.host, rates: r.rates }), + }, + { + label: 'upload', + value: getUploadCost({ host: h.host, rates: r.rates }), + }, + ] + + return getOGImage( + { + id: h.host.public_key, + title: h.host.net_address, + subtitle: h.host.public_key, + initials: 'H', + avatar: true, + values, + }, + size + ) +} diff --git a/apps/explorer/app/host/[id]/page.tsx b/apps/explorer/app/host/[id]/page.tsx new file mode 100644 index 000000000..80e33ea05 --- /dev/null +++ b/apps/explorer/app/host/[id]/page.tsx @@ -0,0 +1,49 @@ +import { + getSiaCentralExchangeRates, + getSiaCentralHost, +} from '@siafoundation/sia-central' +import { Metadata } from 'next' +import { routes } from '../../../config/routes' +import { buildMetadata } from '../../../lib/utils' +import { Host } from '../../../components/Host' +import { siaCentralApi } from '../../../config' +import { notFound } from 'next/navigation' + +export function generateMetadata({ params }): Metadata { + const id = decodeURIComponent((params?.id as string) || '') + const title = `Host ${id}` + const description = `View details for host ${id}` + const url = routes.host.view.replace(':id', id) + return buildMetadata({ + title, + description, + url, + }) +} + +export const revalidate = 60 + +export default async function Page({ params }) { + const id = params?.id as string + const [h, r] = await Promise.all([ + getSiaCentralHost({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }), + getSiaCentralExchangeRates({ + config: { + api: siaCentralApi, + }, + }), + ]) + + if (!h.host) { + return notFound() + } + + return +} diff --git a/apps/explorer-v1/public/android-chrome-512x512.png b/apps/explorer/app/icon.png similarity index 100% rename from apps/explorer-v1/public/android-chrome-512x512.png rename to apps/explorer/app/icon.png diff --git a/apps/explorer/app/layout.tsx b/apps/explorer/app/layout.tsx new file mode 100644 index 000000000..49ebf1ae0 --- /dev/null +++ b/apps/explorer/app/layout.tsx @@ -0,0 +1,30 @@ +import { Layout } from '../components/Layout' +import '../config/style.css' +import { NextAppSsrAppRouter } from '@siafoundation/design-system' +import { appLink } from '../config' + +export const metadata = { + title: 'Explorer', + description: 'Sia blockchain explorer', + metadataBase: new URL( + process.env.NODE_ENV === 'development' + ? `http://localhost:${process.env.PORT || 3000}` + : appLink + ), +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + {children} + + + + ) +} diff --git a/apps/explorer/app/loading.tsx b/apps/explorer/app/loading.tsx new file mode 100644 index 000000000..27cbdce3d --- /dev/null +++ b/apps/explorer/app/loading.tsx @@ -0,0 +1,5 @@ +import { HomeSkeleton } from '../components/HomeSkeleton' + +export default function Loading() { + return +} diff --git a/apps/explorer/app/manifest.ts b/apps/explorer/app/manifest.ts new file mode 100644 index 000000000..869e28010 --- /dev/null +++ b/apps/explorer/app/manifest.ts @@ -0,0 +1,20 @@ +import { MetadataRoute } from 'next' + +export default function manifest(): MetadataRoute.Manifest { + return { + name: 'Sia - Explorer', + short_name: 'Sia', + description: 'Sia blockchain explorer', + start_url: '/', + display: 'standalone', + background_color: '#fff', + theme_color: '#fff', + icons: [ + { + src: '/favicon.ico', + sizes: 'any', + type: 'image/x-icon', + }, + ], + } +} diff --git a/apps/explorer/app/opengraph-image.tsx b/apps/explorer/app/opengraph-image.tsx new file mode 100644 index 000000000..88c1b6496 --- /dev/null +++ b/apps/explorer/app/opengraph-image.tsx @@ -0,0 +1,62 @@ +import { + getSiaCentralBlockLatest, + getSiaCentralHostsNetworkMetrics, +} from '@siafoundation/sia-central' +import { getOGImage } from '../components/OGImage' +import { networkName, siaCentralApi } from '../config' +import { humanBytes } from '@siafoundation/sia-js' + +export const revalidate = 60 + +export const alt = 'Contract' +export const size = { + width: 1200, + height: 630, +} + +export const contentType = 'image/png' + +export default async function Image() { + const [metrics, lastestBlock] = await Promise.all([ + getSiaCentralHostsNetworkMetrics({ + config: { + api: siaCentralApi, + }, + }), + getSiaCentralBlockLatest({ + config: { + api: siaCentralApi, + }, + }), + ]) + const lastBlockHeight = Number(lastestBlock?.block.height || 0) + + const values = [ + { + label: 'Block height', + value: lastBlockHeight.toLocaleString(), + }, + { + label: 'Active hosts', + value: metrics.totals.active_hosts.toLocaleString(), + }, + { + label: 'Used storage', + value: humanBytes( + metrics.totals.total_storage - metrics.totals.remaining_storage + ), + }, + ] + + return getOGImage( + { + title: networkName === 'Sia Mainnet' ? 'Sia Explorer' : 'Zen Explorer', + subtitle: + networkName === 'Sia Mainnet' + ? 'Sia blockchain and host explorer.' + : 'Zen testnet blockchain and host explorer.', + values, + }, + size + ) +} diff --git a/apps/explorer/app/page.tsx b/apps/explorer/app/page.tsx new file mode 100644 index 000000000..cedd0aad8 --- /dev/null +++ b/apps/explorer/app/page.tsx @@ -0,0 +1,68 @@ +import { Metadata } from 'next' +import { appLink, appName, networkName, siaCentralApi } from '../config' +import { Home } from '../components/Home' +import { + getSiaCentralBlockLatest, + getSiaCentralBlocks, + getSiaCentralHosts, + getSiaCentralHostsNetworkMetrics, +} from '@siafoundation/sia-central' +import { range } from 'lodash' + +export function generateMetadata(): Metadata { + const title = 'Home' + const description = + networkName === 'Sia Mainnet' + ? 'Sia blockchain explorer' + : 'Zen blockchain explorer' + return { + title, + description, + openGraph: { + title, + description, + url: appLink, + siteName: appName, + }, + } +} + +export const revalidate = 60 + +export default async function HomePage() { + const metrics = await getSiaCentralHostsNetworkMetrics({ + config: { + api: siaCentralApi, + }, + }) + const lastestBlock = await getSiaCentralBlockLatest({ + config: { + api: siaCentralApi, + }, + }) + const lastBlockHeight = lastestBlock?.block.height || 0 + const blocks = await getSiaCentralBlocks({ + payload: { + heights: range(lastBlockHeight - 5, lastBlockHeight), + }, + config: { + api: siaCentralApi, + }, + }) + const hosts = await getSiaCentralHosts({ + params: { + limit: 5, + }, + config: { + api: siaCentralApi, + }, + }) + return ( + + ) +} diff --git a/apps/explorer/app/tx/[id]/error.tsx b/apps/explorer/app/tx/[id]/error.tsx new file mode 100644 index 000000000..9e195358b --- /dev/null +++ b/apps/explorer/app/tx/[id]/error.tsx @@ -0,0 +1,9 @@ +'use client' + +import { StateError } from '../../../components/StateError' + +export default function Page() { + return ( + + ) +} diff --git a/apps/explorer/app/tx/[id]/loading.tsx b/apps/explorer/app/tx/[id]/loading.tsx new file mode 100644 index 000000000..7e4a67f53 --- /dev/null +++ b/apps/explorer/app/tx/[id]/loading.tsx @@ -0,0 +1,5 @@ +import { TransactionSkeleton } from '../../../components/TransactionSkeleton' + +export default function Loading() { + return +} diff --git a/apps/explorer/app/tx/[id]/opengraph-image.tsx b/apps/explorer/app/tx/[id]/opengraph-image.tsx new file mode 100644 index 000000000..9ddb53368 --- /dev/null +++ b/apps/explorer/app/tx/[id]/opengraph-image.tsx @@ -0,0 +1,58 @@ +import { getSiaCentralTransaction } from '@siafoundation/sia-central' +import { humanDate } from '@siafoundation/sia-js' +import { getOGImage } from '../../../components/OGImageEntity' +import { siaCentralApi } from '../../../config' +import { truncate } from '@siafoundation/design-system' + +export const revalidate = 60 + +export const alt = 'Transaction' +export const size = { + width: 1200, + height: 630, +} + +export const contentType = 'image/png' + +export default async function Image({ params }) { + const id = params?.id as string + + const t = await getSiaCentralTransaction({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }) + + const values = [ + { + label: 'block height', + value: t.transaction.block_height.toLocaleString(), + }, + { + label: 'time', + value: humanDate(t.transaction.timestamp, { + dateStyle: 'medium', + timeStyle: 'short', + }), + }, + ] + + return getOGImage( + { + id, + title: truncate(t.transaction.id, 30), + subtitle: 'transaction', + status: + t.transaction.confirmations >= 72 + ? '72+ confirmations' + : `${t.transaction.confirmations} confirmations`, + statusColor: t.transaction.confirmations >= 6 ? 'green' : 'amber', + initials: 'T', + values, + }, + size + ) +} diff --git a/apps/explorer/app/tx/[id]/page.tsx b/apps/explorer/app/tx/[id]/page.tsx new file mode 100644 index 000000000..607b25b67 --- /dev/null +++ b/apps/explorer/app/tx/[id]/page.tsx @@ -0,0 +1,39 @@ +import { Metadata } from 'next' +import { routes } from '../../../config/routes' +import { Transaction } from '../../../components/Transaction' +import { getSiaCentralTransaction } from '@siafoundation/sia-central' +import { buildMetadata } from '../../../lib/utils' +import { siaCentralApi } from '../../../config' +import { notFound } from 'next/navigation' + +export function generateMetadata({ params }): Metadata { + const id = decodeURIComponent((params?.id as string) || '') + const title = `Transaction ${id}` + const description = `View details for transaction ${id}` + const url = routes.transaction.view.replace(':id', id) + return buildMetadata({ + title, + description, + url, + }) +} + +export const revalidate = 60 + +export default async function Page({ params }) { + const id = params?.id as string + const transaction = await getSiaCentralTransaction({ + params: { + id, + }, + config: { + api: siaCentralApi, + }, + }) + + if (!transaction.transaction) { + return notFound() + } + + return +} diff --git a/apps/explorer-v1/assets/leaves-background.png b/apps/explorer/assets/leaves-background.png similarity index 100% rename from apps/explorer-v1/assets/leaves-background.png rename to apps/explorer/assets/leaves-background.png diff --git a/apps/explorer-v1/assets/leaves-preview.png b/apps/explorer/assets/leaves-preview.png similarity index 100% rename from apps/explorer-v1/assets/leaves-preview.png rename to apps/explorer/assets/leaves-preview.png diff --git a/apps/explorer/components/Address/index.tsx b/apps/explorer/components/Address/index.tsx new file mode 100644 index 000000000..51bbde8ae --- /dev/null +++ b/apps/explorer/components/Address/index.tsx @@ -0,0 +1,173 @@ +'use client' + +import { + Badge, + Tabs, + TabsContent, + TabsList, + TabsTrigger, + Tooltip, + EntityList, + EntityListItemProps, +} from '@siafoundation/design-system' +import { humanNumber } from '@siafoundation/sia-js' +import { ExplorerDatum, DatumProps } from '../ExplorerDatum' +import { useMemo, useState } from 'react' +import { routes } from '../../config/routes' +import { EntityHeading } from '../EntityHeading' +import BigNumber from 'bignumber.js' +import { ContentLayout } from '../ContentLayout' +import { SiaCentralAddressResponse } from '@siafoundation/sia-central' + +type Tab = 'transactions' | 'evolution' | 'utxos' + +type Props = { + id: string + address: SiaCentralAddressResponse +} + +export function Address({ id, address }: Props) { + const [tab, setTab] = useState('transactions') + + const values = useMemo(() => { + const list: DatumProps[] = [ + { + label: 'SC', + sc: new BigNumber(address.unspent_siacoins), + }, + ] + if (address.unspent_siafunds !== '0') { + list.push({ + label: 'SF', + sc: new BigNumber(address.unspent_siafunds), + }) + } + return list + }, [address]) + + const transactions = useMemo(() => { + const list: EntityListItemProps[] = [] + if (address.unconfirmed_transactions?.length) { + list.push( + ...address.unconfirmed_transactions.map((tx) => ({ + hash: tx.id, + sc: getTotal({ + inputs: tx.siacoin_inputs, + outputs: tx.siacoin_outputs, + }), + sf: getTotal({ + inputs: tx.siafund_inputs, + outputs: tx.siafund_outputs, + }).toNumber(), + label: 'Transaction', + initials: 'T', + href: routes.transaction.view.replace(':id', tx.id), + height: tx.block_height, + timestamp: new Date(tx.timestamp).getTime(), + })) + ) + } + if (address.transactions?.length) { + list.push( + ...address.transactions.map((tx) => ({ + hash: tx.id, + sc: getTotal({ + inputs: tx.siacoin_inputs, + outputs: tx.siacoin_outputs, + }), + sf: getTotal({ + inputs: tx.siafund_inputs, + outputs: tx.siafund_outputs, + }).toNumber(), + label: 'Transaction', + initials: 'T', + href: routes.transaction.view.replace(':id', tx.id), + height: tx.block_height, + timestamp: new Date(tx.timestamp).getTime(), + })) + ) + } + return list + }, [address]) + + const utxos = useMemo(() => { + const list: EntityListItemProps[] = [] + if (address.unspent_siacoin_outputs?.length) { + list.push( + ...address.unspent_siacoin_outputs.map((o) => ({ + hash: o.output_id, + sc: new BigNumber(o.value), + label: 'siacoin output', + initials: 'SO', + scVariant: 'value' as const, + height: o.block_height, + })) + ) + } + return list + }, [address]) + + return ( + +
+ +
+ + + {`${humanNumber(address.transactions?.length)}`} transactions + + +
+
+
+ {values.map((item) => ( + + ))} +
+ + } + > + setTab(val as Tab)} + > + + Transactions + Unspent outputs + + + + + + + + +
+ ) +} + +function getTotal({ + inputs, + outputs, +}: { + inputs?: { value: string }[] + outputs?: { value: string }[] +}) { + return (outputs || []) + .reduce((acc, o) => acc.plus(o.value), new BigNumber(0)) + .minus( + (inputs || []).reduce((acc, i) => acc.plus(i.value), new BigNumber(0)) + ) +} diff --git a/apps/explorer-v1/components/entities/AddressEntitySkeleton/index.tsx b/apps/explorer/components/AddressSkeleton/index.tsx similarity index 58% rename from apps/explorer-v1/components/entities/AddressEntitySkeleton/index.tsx rename to apps/explorer/components/AddressSkeleton/index.tsx index 43963e4a6..928a7c3e6 100644 --- a/apps/explorer-v1/components/entities/AddressEntitySkeleton/index.tsx +++ b/apps/explorer/components/AddressSkeleton/index.tsx @@ -8,16 +8,16 @@ import { DatumSkeleton, } from '@siafoundation/design-system' import { times } from 'lodash' -import { ContentLayout } from '../../ContentLayout' +import { ContentLayout } from '../ContentLayout' -export function AddressEntitySkeleton() { +export function AddressSkeleton() { return ( +
-
- {times(4, (i) => ( +
+ {times(1, (i) => ( ))}
@@ -26,15 +26,15 @@ export function AddressEntitySkeleton() { > - Last 100 transactions - History + Transactions Unspent outputs - - + + + ) diff --git a/apps/explorer/components/Block/index.tsx b/apps/explorer/components/Block/index.tsx new file mode 100644 index 000000000..732835644 --- /dev/null +++ b/apps/explorer/components/Block/index.tsx @@ -0,0 +1,75 @@ +import { Badge, Tooltip, EntityList } from '@siafoundation/design-system' +import { humanNumber } from '@siafoundation/sia-js' +import { ExplorerDatum, DatumProps } from '../ExplorerDatum' +import { useMemo } from 'react' +import { routes } from '../../config/routes' +import { EntityHeading } from '../EntityHeading' +import { ContentLayout } from '../ContentLayout' +import { SiaCentralBlock } from '@siafoundation/sia-central' + +type Props = { + block: SiaCentralBlock +} + +export function Block({ block }: Props) { + const values = useMemo(() => { + const list: DatumProps[] = [ + { + label: 'Block hash', + value: block.id, + }, + { + label: 'Miner payout address', + entityType: 'address', + entityValue: block.siacoin_outputs?.find( + (output) => output.source === 'miner_payout' + )?.unlock_hash, + }, + ] + return list + }, [block]) + + return ( + +
+ +
+ + + {`${humanNumber(block.transactions?.length || 0)}`}{' '} + transactions + + +
+
+
+ {values.map((item) => ( + + ))} +
+
+ } + > + ({ + hash: tx.id, + label: 'transaction', + initials: 'T', + href: routes.transaction.view.replace(':id', tx.id), + }))} + /> + + ) +} diff --git a/apps/explorer-v1/components/entities/BlockEntitySkeleton/index.tsx b/apps/explorer/components/BlockSkeleton/index.tsx similarity index 76% rename from apps/explorer-v1/components/entities/BlockEntitySkeleton/index.tsx rename to apps/explorer/components/BlockSkeleton/index.tsx index 3c621cd61..cc1ad04fb 100644 --- a/apps/explorer-v1/components/entities/BlockEntitySkeleton/index.tsx +++ b/apps/explorer/components/BlockSkeleton/index.tsx @@ -4,9 +4,9 @@ import { DatumSkeleton, } from '@siafoundation/design-system' import { times } from 'lodash' -import { ContentLayout } from '../../ContentLayout' +import { ContentLayout } from '../ContentLayout' -export function BlockEntitySkeleton() { +export function BlockSkeleton() { return (
- {times(5, (i) => ( + {times(2, (i) => ( ))}
- -
} diff --git a/apps/explorer/components/ContentLayout.tsx b/apps/explorer/components/ContentLayout.tsx new file mode 100644 index 000000000..6cc52d1ad --- /dev/null +++ b/apps/explorer/components/ContentLayout.tsx @@ -0,0 +1,23 @@ +import { Container, Panel } from '@siafoundation/design-system' +import React from 'react' + +type Props = { + heading?: React.ReactNode + panel?: React.ReactNode + children?: React.ReactNode + className?: string +} + +export function ContentLayout({ heading, panel, children, className }: Props) { + return ( + <> + {heading && {heading}} + {panel && ( + + {panel} + + )} + {children && {children}} + + ) +} diff --git a/apps/explorer/components/Contract/ContractHeader.tsx b/apps/explorer/components/Contract/ContractHeader.tsx new file mode 100644 index 000000000..26a52cdbb --- /dev/null +++ b/apps/explorer/components/Contract/ContractHeader.tsx @@ -0,0 +1,91 @@ +import { + ArrowLeft16, + ArrowRight16, + Badge, + ContractTimeline, + LinkButton, +} from '@siafoundation/design-system' +import { + SiaCentralContract, + getSiaCentralBlockLatest, +} from '@siafoundation/sia-central' +import { lowerCase } from 'lodash' +import { routes } from '../../config/routes' +import { EntityHeading } from '../EntityHeading' +import { siaCentralApi } from '../../config' + +type Props = { + contract: SiaCentralContract + renewedToId?: string + renewedFromId?: string +} + +export async function ContractHeader({ + contract, + renewedFromId, + renewedToId, +}: Props) { + const { id } = contract + const latest = await getSiaCentralBlockLatest({ + config: { + api: siaCentralApi, + }, + }) + return ( +
+
+ +
+ {renewedFromId && renewedFromId !== id && ( + + + Renewed from + + )} + + {lowerCase(contract.status)} + + {renewedToId && renewedToId !== id && ( + + Renewed to + + + )} +
+
+
+ +
+
+ ) +} diff --git a/apps/explorer/components/Contract/index.tsx b/apps/explorer/components/Contract/index.tsx new file mode 100644 index 000000000..c2d40fd70 --- /dev/null +++ b/apps/explorer/components/Contract/index.tsx @@ -0,0 +1,164 @@ +'use client' + +import { useMemo } from 'react' +import BigNumber from 'bignumber.js' +import { + SiaCentralContract, + SiaCentralExchangeRates, +} from '@siafoundation/sia-central' +import { humanBytes, humanDate, humanSiacoin } from '@siafoundation/sia-js' +import { EntityList, EntityListItemProps } from '@siafoundation/design-system' +import { DatumProps, ExplorerDatum } from '../ExplorerDatum' +import { ContentLayout } from '../ContentLayout' +import { ContractHeader } from './ContractHeader' +import { siacoinToDollars } from '../../lib/currency' + +type Props = { + contract: SiaCentralContract + rates: SiaCentralExchangeRates + renewedTo?: SiaCentralContract + renewedFrom?: SiaCentralContract +} + +export function Contract({ contract, rates, renewedFrom, renewedTo }: Props) { + const values = useMemo(() => { + return [ + { + label: 'File Size', + copyable: false, + value: humanBytes(contract.file_size), + }, + { + label: 'Payout', + copyable: false, + value: `${siacoinToDollars(contract.payout, rates)} (${humanSiacoin( + contract.payout + )})`, + }, + { + label: 'Transaction ID', + entityType: 'transaction', + entityValue: contract.transaction_id, + }, + { + label: 'Merkle Root', + value: contract.merkle_root, + }, + { + label: 'Unlock Hash', + value: contract.unlock_hash, + }, + { + label: 'Revision Number', + value: contract.revision_number.toLocaleString(), + }, + { + label: 'Negotiation Height', + value: contract.negotiation_height?.toLocaleString() || '-', + }, + { + label: 'Negotiation Time', + copyable: false, + value: + contract.negotiation_timestamp !== '0001-01-01T00:00:00Z' + ? humanDate(contract.negotiation_timestamp, { + dateStyle: 'medium', + timeStyle: 'short', + }) + : '-', + }, + { + label: 'Payout Height', + value: contract.payout_height?.toLocaleString() || '-', + }, + { + label: 'Payout Time', + copyable: false, + value: + contract.payout_timestamp !== '0001-01-01T00:00:00Z' + ? humanDate(contract.payout_timestamp, { + dateStyle: 'medium', + timeStyle: 'short', + }) + : '-', + }, + { + label: 'Proof Confirmed', + value: String(contract.proof_confirmed), + }, + { + label: 'Previous Revisions', + copyable: false, + value: (contract.previous_revisions?.length || 0).toLocaleString(), + }, + ] as DatumProps[] + }, [contract, rates]) + + const missedProofOutputs = useMemo(() => { + if (!contract) { + return [] + } + const list: EntityListItemProps[] = [] + contract.missed_proof_outputs?.forEach((o) => { + list.push({ + label: o.source ? o.source.replace(/_/g, ' ') : 'output', + sc: new BigNumber(o.value), + hash: o.output_id, + }) + }) + return list + }, [contract]) + + const validProofOutputs = useMemo(() => { + if (!contract) { + return [] + } + const list: EntityListItemProps[] = [] + contract.valid_proof_outputs?.forEach((o) => { + list.push({ + label: o.source ? o.source.replace(/_/g, ' ') : 'output', + sc: new BigNumber(o.value), + hash: o.output_id, + }) + }) + return list + }, [contract]) + + return ( + + + {!!values?.length && ( +
+ {values.map((item) => ( + + ))} +
+ )} + + } + > +
+
+
+ +
+
+ +
+
+
+
+ ) +} diff --git a/apps/explorer-v1/components/entities/TxEntitySkeleton/index.tsx b/apps/explorer/components/ContractSkeleton/index.tsx similarity index 51% rename from apps/explorer-v1/components/entities/TxEntitySkeleton/index.tsx rename to apps/explorer/components/ContractSkeleton/index.tsx index 44da7f040..8444d8ddd 100644 --- a/apps/explorer-v1/components/entities/TxEntitySkeleton/index.tsx +++ b/apps/explorer/components/ContractSkeleton/index.tsx @@ -4,19 +4,20 @@ import { DatumSkeleton, } from '@siafoundation/design-system' import { times } from 'lodash' -import { ContentLayout } from '../../ContentLayout' +import { ContentLayout } from '../ContentLayout' -export function TxEntitySkeleton() { +export function ContractSkeleton() { return ( +
-
- {times(3, (i) => ( + +
+ {times(12, (i) => ( ))}
@@ -25,10 +26,18 @@ export function TxEntitySkeleton() { >
- +
- +
diff --git a/apps/explorer/components/ContractView/index.tsx b/apps/explorer/components/ContractView/index.tsx new file mode 100644 index 000000000..6c561e4ca --- /dev/null +++ b/apps/explorer/components/ContractView/index.tsx @@ -0,0 +1,41 @@ +import { Container, Separator } from '@siafoundation/design-system' +import { Transaction } from '../Transaction' +import { + SiaCentralContract, + SiaCentralExchangeRates, + SiaCentralTransaction, +} from '@siafoundation/sia-central' +import { Contract } from '../Contract' + +type Props = { + contract: SiaCentralContract + rates: SiaCentralExchangeRates + renewedTo?: SiaCentralContract + renewedFrom?: SiaCentralContract + formationTransaction: SiaCentralTransaction +} + +export function ContractView({ + contract, + rates, + renewedFrom, + renewedTo, + formationTransaction, +}: Props) { + return ( + <> + + + + + {formationTransaction ? ( + + ) : null} + + ) +} diff --git a/apps/explorer-v1/components/EntityHeading.tsx b/apps/explorer/components/EntityHeading.tsx similarity index 80% rename from apps/explorer-v1/components/EntityHeading.tsx rename to apps/explorer/components/EntityHeading.tsx index ec222ec5c..5fa588f51 100644 --- a/apps/explorer-v1/components/EntityHeading.tsx +++ b/apps/explorer/components/EntityHeading.tsx @@ -1,3 +1,5 @@ +'use client' + import { copyToClipboard, Heading, @@ -5,13 +7,13 @@ import { Button, Link, Copy16, + EntityType, } from '@siafoundation/design-system' -import { NvgEntityType, getNvgEntityTypeLabel } from '../config/navigatorTypes' import { upperFirst } from 'lodash' type Props = { label: string - type: NvgEntityType + type: EntityType value: string href: string } @@ -29,7 +31,7 @@ export function EntityHeading({ label, type, value, href }: Props) { diff --git a/apps/explorer-v1/components/NvgDatum.tsx b/apps/explorer/components/ExplorerDatum.tsx similarity index 74% rename from apps/explorer-v1/components/NvgDatum.tsx rename to apps/explorer/components/ExplorerDatum.tsx index b6cd05751..7b973c1c5 100644 --- a/apps/explorer-v1/components/NvgDatum.tsx +++ b/apps/explorer/components/ExplorerDatum.tsx @@ -3,30 +3,30 @@ import { ValueSf, ValueSc, ValueCopyable, + EntityType, } from '@siafoundation/design-system' import BigNumber from 'bignumber.js' import { upperFirst } from 'lodash' -import { NvgEntityType, getNvgEntityTypeLabel } from '../config/navigatorTypes' -import { getHrefForType } from '../lib/utils' +import { getHref } from '../lib/utils' // entityType&entityValue | value | values | sc | sf export type DatumProps = { label: string value?: React.ReactNode - hash?: string - entityType?: NvgEntityType + entityType?: EntityType entityValue?: string sc?: BigNumber sf?: number comment?: React.ReactNode + copyable?: boolean } -export function NvgDatum({ +export function ExplorerDatum({ label, entityType, entityValue, + copyable = true, value, - hash, sc, sf, comment, @@ -34,11 +34,11 @@ export function NvgDatum({ return (
- + {upperFirst(label)}
-
+
{sc !== undefined && ( )} @@ -49,8 +49,8 @@ export function NvgDatum({ (entityValue ? ( ))} - {hash && } - {value !== undefined && ( + {value !== undefined && copyable && ( + + )} + {value !== undefined && !copyable && ( {value} diff --git a/apps/explorer-v1/components/Faucet/FaucetFundForm.tsx b/apps/explorer/components/Faucet/FaucetFundForm.tsx similarity index 94% rename from apps/explorer-v1/components/Faucet/FaucetFundForm.tsx rename to apps/explorer/components/Faucet/FaucetFundForm.tsx index d56993823..b4116359f 100644 --- a/apps/explorer-v1/components/Faucet/FaucetFundForm.tsx +++ b/apps/explorer/components/Faucet/FaucetFundForm.tsx @@ -44,14 +44,16 @@ export function FaucetFundForm({ onDone }: Props) { const response = await fund.post({ payload: { unlockHash: values.address, - amount: toHastings(values.amount).toString(), + amount: toHastings(values.amount || 0).toString(), }, }) if (response.error) { actions.setStatus({ error: response.error }) } else { triggerToast('Address has been funded.') - onDone(response.data?.id) + if (response.data) { + onDone(response.data.id) + } actions.resetForm() } }, diff --git a/apps/explorer-v1/components/Faucet/FaucetStatus.tsx b/apps/explorer/components/Faucet/FaucetStatus.tsx similarity index 96% rename from apps/explorer-v1/components/Faucet/FaucetStatus.tsx rename to apps/explorer/components/Faucet/FaucetStatus.tsx index 77b53c5d1..19ca9bc93 100644 --- a/apps/explorer-v1/components/Faucet/FaucetStatus.tsx +++ b/apps/explorer/components/Faucet/FaucetStatus.tsx @@ -39,7 +39,7 @@ export function FaucetStatus({ id: _id, setId }: Props) { useEffect(() => { setSuccess(false) - status.mutate(null) + status.mutate(undefined) // eslint-disable-next-line react-hooks/exhaustive-deps }, [id]) @@ -126,7 +126,10 @@ export function FaucetStatus({ id: _id, setId }: Props) { ) : ( diff --git a/apps/explorer/components/Faucet/index.tsx b/apps/explorer/components/Faucet/index.tsx new file mode 100644 index 000000000..d860e195f --- /dev/null +++ b/apps/explorer/components/Faucet/index.tsx @@ -0,0 +1,71 @@ +'use client' + +import { + Container, + FaucetIcon, + Heading, + Panel, + Tabs, + TabsContent, + TabsList, + TabsTrigger, + Text, + webLinks, +} from '@siafoundation/design-system' +import { useCallback, useEffect, useState } from 'react' +import { FaucetFundForm } from './FaucetFundForm' +import { FaucetStatus } from './FaucetStatus' +import { isMainnet, networkName } from '../../config' + +type Tab = 'fund' | 'status' + +export function Faucet() { + const [id, setId] = useState('') + const [tab, setTab] = useState('fund') + + useEffect(() => { + if (isMainnet) { + window.location.replace(webLinks.explore.testnetFaucet) + } + }, []) + + const onDone = useCallback( + (id: string) => { + setId(id) + setTab('status') + }, + [setId, setTab] + ) + + return ( + +
+ +
+ + + + {networkName} Faucet + setTab(val as Tab)} + className="w-full" + > + + Fund + Status + + + + + + + + +
+
+
+
+ ) +} diff --git a/apps/explorer/components/FaucetSkeleton/index.tsx b/apps/explorer/components/FaucetSkeleton/index.tsx new file mode 100644 index 000000000..6aeb0764d --- /dev/null +++ b/apps/explorer/components/FaucetSkeleton/index.tsx @@ -0,0 +1,25 @@ +import { + Container, + Panel, + Text, + FaucetIcon, + Heading, +} from '@siafoundation/design-system' +import { networkName } from '../../config' + +export function FaucetSkeleton() { + return ( + +
+ +
+ + + + {networkName} Faucet +
+
+
+
+ ) +} diff --git a/apps/explorer/components/Home/index.tsx b/apps/explorer/components/Home/index.tsx new file mode 100644 index 000000000..755a4d718 --- /dev/null +++ b/apps/explorer/components/Home/index.tsx @@ -0,0 +1,178 @@ +import { + Text, + BlockList, + Tooltip, + EntityList, +} from '@siafoundation/design-system' +import { humanBytes, humanNumber } from '@siafoundation/sia-js' +import { useMemo } from 'react' +import { routes } from '../../config/routes' +import { ContentLayout } from '../ContentLayout' +import { reverse, sortBy } from 'lodash' +import { + SiaCentralBlock, + SiaCentralHost, + SiaCentralHostsNetworkMetricsResponse, +} from '@siafoundation/sia-central' +import { hashToAvatar } from '../../lib/avatar' + +export function Home({ + metrics, + blockHeight, + blocks, + hosts, +}: { + metrics: SiaCentralHostsNetworkMetricsResponse + blockHeight: number + blocks: SiaCentralBlock[] + hosts: SiaCentralHost[] +}) { + const values = useMemo(() => { + const list = [ + { + label: 'Blockchain height', + value: ( +
+ {humanNumber(blockHeight)} + {/* {status.data && + status.data.consensusblock !== status.data.lastblock && ( + <> + + / + + + + {humanNumber(status.data?.consensusblock)} + + + + )} */} +
+ ), + }, + { + label: 'Storage utilization', + value: ( +
+ + + {humanBytes( + metrics?.totals.total_storage - + metrics?.totals.remaining_storage + )} + + + + / + + + + {humanBytes(metrics?.totals.total_storage)} + + +
+ ), + }, + { + label: 'Active hosts', + value: ( +
+ + + {humanNumber(metrics?.totals.active_hosts)} + + + + / + + + + {humanNumber(metrics?.totals.total_hosts)} + + +
+ ), + }, + ] + return list + }, [metrics, blockHeight]) + + return ( + + {values.map(({ label, value }) => ( +
+ + {label} + + + {value} + +
+ ))} +
+ } + > +
+
+ ({ + height: block.height, + timestamp: block.timestamp, + href: routes.block.view.replace(':id', String(block.height)), + }))} + /> +
+
+ ({ + label: host.net_address, + initials: 'H', + avatar: hashToAvatar(host.public_key), + href: routes.host.view.replace(':id', host.public_key), + hash: host.public_key, + }))} + /> +
+
+ + ) +} diff --git a/apps/explorer/components/HomeSkeleton/index.tsx b/apps/explorer/components/HomeSkeleton/index.tsx new file mode 100644 index 000000000..a8713e2da --- /dev/null +++ b/apps/explorer/components/HomeSkeleton/index.tsx @@ -0,0 +1,21 @@ +import { Skeleton, BlockList, EntityList } from '@siafoundation/design-system' +import { ContentLayout } from '../ContentLayout' + +export function HomeSkeleton() { + return ( + + + + +
+ } + > +
+ + +
+ + ) +} diff --git a/apps/explorer/components/Host/HostHeader.tsx b/apps/explorer/components/Host/HostHeader.tsx new file mode 100644 index 000000000..c6602c080 --- /dev/null +++ b/apps/explorer/components/Host/HostHeader.tsx @@ -0,0 +1,38 @@ +import { Avatar } from '@siafoundation/design-system' +import { + SiaCentralExchangeRates, + SiaCentralHost, +} from '@siafoundation/sia-central' +import { hashToAvatar } from '../../lib/avatar' +import { HostPricing } from './HostPricing' +import { HostInfo } from './HostInfo' + +type Props = { + host: SiaCentralHost + rates: SiaCentralExchangeRates +} + +export function HostHeader({ host, rates }: Props) { + return ( +
+ +
+ +
+ + +
+
+
+ ) +} diff --git a/apps/explorer/components/Host/HostInfo.tsx b/apps/explorer/components/Host/HostInfo.tsx new file mode 100644 index 000000000..7c78ca1c6 --- /dev/null +++ b/apps/explorer/components/Host/HostInfo.tsx @@ -0,0 +1,114 @@ +import { + CheckmarkFilled16, + Fork16, + Separator, + Text, + Time16, + Tooltip, + ValueCopyable, + WarningFilled16, + countryCodeEmoji, +} from '@siafoundation/design-system' +import { SiaCentralHost } from '@siafoundation/sia-central' +import { humanDate } from '@siafoundation/sia-js' +import { formatDistance } from 'date-fns' + +type Props = { + host: SiaCentralHost +} + +export function HostInfo({ host }: Props) { + return ( +
+ + + +
+ + + {host.online ? : } + {host.online ? 'Online' : 'Offline'} + + {host.estimated_uptime}% + + + + + + {host.settings.accepting_contracts ? ( + + ) : ( + + )} + {host.settings.accepting_contracts + ? 'Accepting contracts' + : 'Not accepting contracts'} + + + + + + {host.version} + + + +
+ {countryCodeEmoji(host.country_code)} + + {host.country_code} + +
+
+ + + + {formatDistance( + new Date(host.first_seen_timestamp), + new Date() + )}{' '} + old + + +
+
+ ) +} diff --git a/apps/explorer/components/Host/HostPriceTable.tsx b/apps/explorer/components/Host/HostPriceTable.tsx new file mode 100644 index 000000000..7db4b0e52 --- /dev/null +++ b/apps/explorer/components/Host/HostPriceTable.tsx @@ -0,0 +1,164 @@ +'use client' + +import { useMemo } from 'react' +import { SiaCentralHost } from '@siafoundation/sia-central' +import { DatumProps, ExplorerDatum } from '../ExplorerDatum' +import { Panel } from '@siafoundation/design-system' +import BigNumber from 'bignumber.js' + +type Props = { + host: SiaCentralHost +} + +export function HostPriceTable({ host }: Props) { + const priceTableValues = useMemo(() => { + return [ + { + label: 'account balance cost', + sc: new BigNumber(host.price_table.accountbalancecost), + }, + { + label: 'collateral cost', + sc: new BigNumber(host.price_table.collateralcost), + }, + { + label: 'contract price', + sc: new BigNumber(host.price_table.contractprice), + }, + { + label: 'download bandwidth cost', + sc: new BigNumber(host.price_table.downloadbandwidthcost), + }, + { + label: 'drop sectors base cost', + sc: new BigNumber(host.price_table.dropsectorsbasecost), + }, + { + label: 'drop sectors unit cost', + sc: new BigNumber(host.price_table.dropsectorsunitcost), + }, + { + label: 'fund account cost', + sc: new BigNumber(host.price_table.fundaccountcost), + }, + { + label: 'has sector base cost', + sc: new BigNumber(host.price_table.hassectorbasecost), + }, + { + label: 'host block height', + value: host.price_table.hostblockheight, + }, + { + label: 'init base cost', + sc: new BigNumber(host.price_table.initbasecost), + }, + { + label: 'latest revision cost', + sc: new BigNumber(host.price_table.latestrevisioncost), + }, + { + label: 'max collateral', + sc: new BigNumber(host.price_table.maxcollateral), + }, + { + label: 'max duration', + value: host.price_table.maxduration, + }, + { + label: 'memory time cost', + sc: new BigNumber(host.price_table.memorytimecost), + }, + { + label: 'read base cost', + sc: new BigNumber(host.price_table.readbasecost), + }, + { + label: 'read length cost', + sc: new BigNumber(host.price_table.readlengthcost), + }, + { + label: 'registry entries left', + value: host.price_table.registryentriesleft, + }, + { + label: 'registry entries total', + value: host.price_table.registryentriestotal, + }, + { + label: 'renew contract cost', + sc: new BigNumber(host.price_table.renewcontractcost), + }, + { + label: 'revision base cost', + sc: new BigNumber(host.price_table.revisionbasecost), + }, + { + label: 'subscription memory cost', + sc: new BigNumber(host.price_table.subscriptionmemorycost), + }, + { + label: 'subscription notification cost', + sc: new BigNumber(host.price_table.subscriptionnotificationcost), + }, + { + label: 'swap sector cost', + sc: new BigNumber(host.price_table.swapsectorcost), + }, + { + label: 'txn fee max recommended', + sc: new BigNumber(host.price_table.txnfeemaxrecommended), + }, + { + label: 'txn fee min recommended', + sc: new BigNumber(host.price_table.txnfeeminrecommended), + }, + { + label: 'UID', + value: host.price_table.uid, + }, + { + label: 'update price table cost', + sc: new BigNumber(host.price_table.updatepricetablecost), + }, + { + label: 'upload bandwidth cost', + sc: new BigNumber(host.price_table.uploadbandwidthcost), + }, + { + label: 'validity', + value: host.price_table.validity, + }, + { + label: 'window size', + value: host.price_table.windowsize, + }, + { + label: 'write base cost', + sc: new BigNumber(host.price_table.writebasecost), + }, + { + label: 'write length cost', + sc: new BigNumber(host.price_table.writelengthcost), + }, + { + label: 'write store cost', + sc: new BigNumber(host.price_table.writestorecost), + }, + ] as DatumProps[] + }, [host]) + + return ( + +
+ {!!priceTableValues?.length && ( +
+ {priceTableValues.map((item) => ( + + ))} +
+ )} +
+
+ ) +} diff --git a/apps/explorer/components/Host/HostPricing.tsx b/apps/explorer/components/Host/HostPricing.tsx new file mode 100644 index 000000000..2918c22e3 --- /dev/null +++ b/apps/explorer/components/Host/HostPricing.tsx @@ -0,0 +1,112 @@ +import { + CloudDownload16, + CloudUpload16, + Text, + Tooltip, + VmdkDisk16, +} from '@siafoundation/design-system' +import { + SiaCentralExchangeRates, + SiaCentralHost, +} from '@siafoundation/sia-central' +import { useMemo } from 'react' +import { humanBytes, humanSpeed } from '@siafoundation/sia-js' +import { getDownloadCost, getStorageCost, getUploadCost } from '../../lib/host' + +type Props = { + host: SiaCentralHost + rates: SiaCentralExchangeRates +} + +export function HostPricing({ host, rates }: Props) { + const storageCost = useMemo( + () => getStorageCost({ host, rates }), + [rates, host] + ) + + const downloadCost = useMemo( + () => getDownloadCost({ host, rates }), + [rates, host] + ) + + const uploadCost = useMemo( + () => getUploadCost({ host, rates }), + [rates, host] + ) + + const downloadSpeed = useMemo( + () => + humanSpeed( + (host.benchmark.data_size * 8) / (host.benchmark.download_time / 1000) + ), + [host] + ) + + const uploadSpeed = useMemo( + () => + humanSpeed( + (host.benchmark.data_size * 8) / (host.benchmark.upload_time / 1000) + ), + [host] + ) + + return ( +
+
+ +
+ + + + + {storageCost} + +
+
+ +
+ + + + + {downloadCost} + +
+
+ +
+ + + + + {uploadCost} + +
+
+
+
+ +
+ + {humanBytes(host.settings.total_storage)} + +
+
+ +
+ {downloadSpeed} +
+
+ +
+ {uploadSpeed} +
+
+
+
+ ) +} diff --git a/apps/explorer/components/Host/HostSettings.tsx b/apps/explorer/components/Host/HostSettings.tsx new file mode 100644 index 000000000..6a6ca4311 --- /dev/null +++ b/apps/explorer/components/Host/HostSettings.tsx @@ -0,0 +1,109 @@ +'use client' + +import { useMemo } from 'react' +import { SiaCentralHost } from '@siafoundation/sia-central' +import { DatumProps, ExplorerDatum } from '../ExplorerDatum' +import { Panel } from '@siafoundation/design-system' +import BigNumber from 'bignumber.js' +import { humanBytes } from '@siafoundation/sia-js' + +type Props = { + host: SiaCentralHost +} + +export function HostSettings({ host }: Props) { + const priceTableValues = useMemo(() => { + return [ + { + label: 'base RPC price', + sc: new BigNumber(host.settings.base_rpc_price), + }, + { + label: 'collateral', + sc: new BigNumber(host.settings.collateral), + }, + { + label: 'contract price', + sc: new BigNumber(host.settings.contract_price), + }, + { + label: 'download price', + sc: new BigNumber(host.settings.download_price), + }, + { + label: 'ephemeral account expiry', + value: host.settings.ephemeral_account_expiry, + }, + { + label: 'max collateral', + sc: new BigNumber(host.settings.max_collateral), + }, + { + label: 'max download batch size', + value: host.settings.max_download_batch_size, + }, + { + label: 'max duration', + value: host.settings.max_duration, + }, + { + label: 'max ephemeral account balance', + sc: new BigNumber(host.settings.max_ephemeral_account_balance), + }, + { + label: 'max revise batch size', + value: host.settings.max_revise_batch_size, + }, + { + label: 'remaining storage', + value: humanBytes(host.settings.remaining_storage), + }, + { + label: 'revision number', + value: host.settings.revision_number, + }, + { + label: 'sector access price', + sc: new BigNumber(host.settings.sector_access_price), + }, + { + label: 'sector size', + value: humanBytes(host.settings.sector_size), + }, + { + label: 'sia mux port', + value: host.settings.sia_mux_port, + }, + { + label: 'storage price', + sc: new BigNumber(host.settings.storage_price), + }, + { + label: 'total storage', + value: humanBytes(host.settings.total_storage), + }, + { + label: 'upload price', + sc: new BigNumber(host.settings.upload_price), + }, + { + label: 'window size', + value: host.settings.window_size, + }, + ] as DatumProps[] + }, [host]) + + return ( + +
+ {!!priceTableValues?.length && ( +
+ {priceTableValues.map((item) => ( + + ))} +
+ )} +
+
+ ) +} diff --git a/apps/explorer/components/Host/index.tsx b/apps/explorer/components/Host/index.tsx new file mode 100644 index 000000000..2d9376bbe --- /dev/null +++ b/apps/explorer/components/Host/index.tsx @@ -0,0 +1,49 @@ +'use client' + +import { useState } from 'react' +import { + SiaCentralExchangeRates, + SiaCentralHost, +} from '@siafoundation/sia-central' +import { ContentLayout } from '../ContentLayout' +import { HostHeader } from './HostHeader' +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from '@siafoundation/design-system' +import { HostPriceTable } from './HostPriceTable' +import { HostSettings } from './HostSettings' + +type Props = { + host: SiaCentralHost + rates: SiaCentralExchangeRates +} + +type Tab = 'priceTable' | 'settings' + +export function Host({ host, rates }: Props) { + const [tab, setTab] = useState('priceTable') + + return ( + }> + setTab(val as Tab)} + > + + Price table (RHPv3) + Settings (RHPv2) + + + + + + + + + + ) +} diff --git a/apps/explorer/components/HostSkeleton/HostHeaderSkeleton.tsx b/apps/explorer/components/HostSkeleton/HostHeaderSkeleton.tsx new file mode 100644 index 000000000..1897af733 --- /dev/null +++ b/apps/explorer/components/HostSkeleton/HostHeaderSkeleton.tsx @@ -0,0 +1,63 @@ +import { + Avatar, + CloudDownload16, + CloudUpload16, + Separator, + Skeleton, + Text, + VmdkDisk16, +} from '@siafoundation/design-system' + +export function HostHeaderSkeleton() { + return ( +
+
+ +
+
+ + + + +
+
+
+
+ + + + +
+
+ + + + +
+
+ + + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ ) +} diff --git a/apps/explorer/components/HostSkeleton/index.tsx b/apps/explorer/components/HostSkeleton/index.tsx new file mode 100644 index 000000000..5b15f1f6b --- /dev/null +++ b/apps/explorer/components/HostSkeleton/index.tsx @@ -0,0 +1,45 @@ +import { DatumSkeleton, Panel } from '@siafoundation/design-system' +import { times } from 'lodash' +import { ContentLayout } from '../ContentLayout' +import { HostHeaderSkeleton } from './HostHeaderSkeleton' +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from '@siafoundation/design-system' + +export function HostSkeleton() { + return ( + }> + + + Price table (RHPv3) + Settings (RHPv2) + + + +
+
+ {times(30).map((i) => ( + + ))} +
+
+
+
+ + +
+
+ {times(30).map((i) => ( + + ))} +
+
+
+
+
+
+ ) +} diff --git a/apps/explorer/components/Layout/Footer.tsx b/apps/explorer/components/Layout/Footer.tsx new file mode 100644 index 000000000..c774c358b --- /dev/null +++ b/apps/explorer/components/Layout/Footer.tsx @@ -0,0 +1,53 @@ +import { + Container, + Link, + webLinks, + Logo, + ThemeRadio, +} from '@siafoundation/design-system' + +export function Footer() { + return ( + +
+
+
+ + + The Sia Foundation © {new Date().getFullYear()} + +
+
+ + Terms of Service + + + Privacy Policy + +
+ +
+
+
+ + ) +} diff --git a/apps/explorer-v1/components/Layout/NavDropdownMenu.tsx b/apps/explorer/components/Layout/NavDropdownMenu.tsx similarity index 84% rename from apps/explorer-v1/components/Layout/NavDropdownMenu.tsx rename to apps/explorer/components/Layout/NavDropdownMenu.tsx index 34e65fe56..94816b243 100644 --- a/apps/explorer-v1/components/Layout/NavDropdownMenu.tsx +++ b/apps/explorer/components/Layout/NavDropdownMenu.tsx @@ -6,7 +6,6 @@ import { webLinks, Link, DropdownMenuGroup, - ThemeRadio, DropdownMenuLabel, } from '@siafoundation/design-system' import { networkName } from '../../config' @@ -41,10 +40,6 @@ export function NavDropdownMenu({ trigger, children, ...props }: Props) { - - Theme - - ) } diff --git a/apps/explorer/components/Layout/Search.tsx b/apps/explorer/components/Layout/Search.tsx new file mode 100644 index 000000000..0beea68b3 --- /dev/null +++ b/apps/explorer/components/Layout/Search.tsx @@ -0,0 +1,121 @@ +'use client' + +import { + Button, + ConfigFields, + ControlGroup, + FieldText, + Search16, + triggerToast, +} from '@siafoundation/design-system' +import React, { useCallback } from 'react' +import { useRouter } from 'next/navigation' +import { useForm } from 'react-hook-form' +import { useSiaCentralSearch } from '@siafoundation/react-sia-central' +import { routes } from '../../config/routes' + +const defaultValues = { + query: '', +} + +const fields: ConfigFields = { + query: { + type: 'text', + title: 'Query', + placeholder: 'Search by host, address, block, transaction, contract ID...', + validation: { + required: 'required', + }, + }, +} + +export function Search() { + const router = useRouter() + + const form = useForm({ + defaultValues, + }) + + const search = useSiaCentralSearch() + + const onSubmit = useCallback( + async (values) => { + const response = await search.get({ + params: { + query: values.query, + }, + }) + + if (!response.data) { + form.setError('query', { message: 'Error connecting to server.' }) + triggerToast('Error connecting to server.') + return + } + + if (response.data.blocks?.length) { + router.push( + routes.block.view.replace( + ':id', + String(response.data.blocks[0].height) + ) + ) + form.reset() + } else if (response.data.transactions?.length) { + router.push( + routes.transaction.view.replace( + ':id', + response.data.transactions[0].id + ) + ) + form.reset() + } else if (response.data.contracts?.length) { + router.push( + routes.contract.view.replace(':id', response.data.contracts[0].id) + ) + form.reset() + } else if (response.data.unlock_hashes?.length) { + router.push( + routes.address.view.replace( + ':id', + response.data.unlock_hashes[0].address + ) + ) + form.reset() + } else if (response.data.hosts?.length) { + router.push( + routes.host.view.replace(':id', response.data.hosts[0].public_key) + ) + form.reset() + } else { + form.setError('query', { message: 'No results match query.' }) + triggerToast('No results match query.') + } + }, + [form, router, search] + ) + + return ( +
+
+ + + + +
+
+ ) +} diff --git a/apps/explorer/components/Layout/index.tsx b/apps/explorer/components/Layout/index.tsx new file mode 100644 index 000000000..76cfe3953 --- /dev/null +++ b/apps/explorer/components/Layout/index.tsx @@ -0,0 +1,49 @@ +import { + NavbarSite, + FaucetIcon, + LinkButton, + Tooltip, +} from '@siafoundation/design-system' +import { routes } from '../../config/routes' +import { Search } from './Search' +import { isMainnet, networkName } from '../../config' +import { NavDropdownMenu } from './NavDropdownMenu' +import { Footer } from './Footer' + +type Props = { + children: React.ReactNode +} + +export function Layout({ children }: Props) { + return ( +
+ {/* */} +
+ + + {!isMainnet && ( + + + + + + )} + + +
+
+ {children} +
+
+
+
+
+ ) +} diff --git a/apps/explorer/components/OGImage/Background.tsx b/apps/explorer/components/OGImage/Background.tsx new file mode 100644 index 000000000..b1b066917 --- /dev/null +++ b/apps/explorer/components/OGImage/Background.tsx @@ -0,0 +1,14 @@ +/* eslint-disable @next/next/no-img-element */ + +export function Background() { + return ( +
+ bg +
+
+ ) +} diff --git a/apps/explorer/components/OGImage/Header.tsx b/apps/explorer/components/OGImage/Header.tsx new file mode 100644 index 000000000..a3be6e32a --- /dev/null +++ b/apps/explorer/components/OGImage/Header.tsx @@ -0,0 +1,26 @@ +export function Header({ + title, + subtitle, +}: { + title: string + subtitle?: string +}) { + return ( +
+
+ {title} + {subtitle && {subtitle}} +
+
+ ) +} diff --git a/apps/explorer/components/OGImage/Preview.tsx b/apps/explorer/components/OGImage/Preview.tsx new file mode 100644 index 000000000..34931e3e6 --- /dev/null +++ b/apps/explorer/components/OGImage/Preview.tsx @@ -0,0 +1,43 @@ +import { Header } from './Header' +import { Background } from './Background' + +type Props = { + title: string + subtitle?: string + values?: { label?: string; value: string }[] +} + +export function Preview({ title, subtitle, values }: Props) { + return ( +
+ +
+
+
+ {values?.map(({ label, value }) => ( +
+ {value} + {label && {label}} +
+ ))} +
+
+
+ ) +} diff --git a/apps/explorer/components/OGImage/index.tsx b/apps/explorer/components/OGImage/index.tsx new file mode 100644 index 000000000..0afe53f4d --- /dev/null +++ b/apps/explorer/components/OGImage/index.tsx @@ -0,0 +1,24 @@ +import { ImageResponse } from 'next/server' +import React from 'react' +import { Preview } from './Preview' + +export async function getOGImage( + props: React.ComponentProps, + size: { width: number; height: number } +) { + const titleFont = await fetch( + new URL('https://sia.tech/assets/fonts/plex-sans-medium.ttf') + ).then((res) => res.arrayBuffer()) + + return new ImageResponse(, { + ...size, + fonts: [ + { + name: 'IBM Plex Sans', + data: titleFont, + style: 'normal', + weight: 500, + }, + ], + }) +} diff --git a/apps/explorer/components/OGImageEntity/Header.tsx b/apps/explorer/components/OGImageEntity/Header.tsx new file mode 100644 index 000000000..f56193fc7 --- /dev/null +++ b/apps/explorer/components/OGImageEntity/Header.tsx @@ -0,0 +1,45 @@ +/* eslint-disable @next/next/no-img-element */ + +import { hashToAvatar } from '../../lib/avatar' +import { cx } from 'class-variance-authority' + +export function Header({ + initials, + avatar, + title, + subtitle, +}: { + initials: string + avatar?: string + title: string + subtitle: string +}) { + return ( +
+ {avatar ? ( + {initials} + ) : ( +
+ {initials} +
+ )} +
+ {title} + {subtitle} +
+
+ ) +} diff --git a/apps/explorer/components/OGImageEntity/Preview.tsx b/apps/explorer/components/OGImageEntity/Preview.tsx new file mode 100644 index 000000000..364353215 --- /dev/null +++ b/apps/explorer/components/OGImageEntity/Preview.tsx @@ -0,0 +1,79 @@ +/* eslint-disable @next/next/no-img-element */ + +import { cx } from 'class-variance-authority' +import { Background } from '../OGImage/Background' +import { Header } from './Header' + +type Props = { + id: string + avatar?: boolean + title: string + subtitle: string + initials: string + status?: string + statusColor?: string + values?: { label?: string; value: string }[] +} + +export function Preview({ + id, + avatar, + title, + subtitle, + initials, + status, + statusColor = 'amber', + values, +}: Props) { + return ( +
+ +
+
+
+ {status && ( +
+ {status} +
+ )} +
+
+ {values?.map(({ label, value }) => ( +
+ {value} + {label && {label}} +
+ ))} +
+
+
+ ) +} diff --git a/apps/explorer/components/OGImageEntity/index.tsx b/apps/explorer/components/OGImageEntity/index.tsx new file mode 100644 index 000000000..0afe53f4d --- /dev/null +++ b/apps/explorer/components/OGImageEntity/index.tsx @@ -0,0 +1,24 @@ +import { ImageResponse } from 'next/server' +import React from 'react' +import { Preview } from './Preview' + +export async function getOGImage( + props: React.ComponentProps, + size: { width: number; height: number } +) { + const titleFont = await fetch( + new URL('https://sia.tech/assets/fonts/plex-sans-medium.ttf') + ).then((res) => res.arrayBuffer()) + + return new ImageResponse(, { + ...size, + fonts: [ + { + name: 'IBM Plex Sans', + data: titleFont, + style: 'normal', + weight: 500, + }, + ], + }) +} diff --git a/apps/explorer-v1/components/entities/Entity404/index.tsx b/apps/explorer/components/StateError/index.tsx similarity index 76% rename from apps/explorer-v1/components/entities/Entity404/index.tsx rename to apps/explorer/components/StateError/index.tsx index 31de0bb84..843014fac 100644 --- a/apps/explorer-v1/components/entities/Entity404/index.tsx +++ b/apps/explorer/components/StateError/index.tsx @@ -1,16 +1,17 @@ import { Panel, Container, Text } from '@siafoundation/design-system' type Props = { + code?: number message: string } -export function Entity404({ message }: Props) { +export function StateError({ code = 404, message }: Props) { return ( - +
- 404 + {code} beep boop diff --git a/apps/explorer/components/Transaction/TransactionHeader.tsx b/apps/explorer/components/Transaction/TransactionHeader.tsx new file mode 100644 index 000000000..533cf7299 --- /dev/null +++ b/apps/explorer/components/Transaction/TransactionHeader.tsx @@ -0,0 +1,49 @@ +import { Badge, Text, Tooltip } from '@siafoundation/design-system' +import { SiaCentralTransaction } from '@siafoundation/sia-central' +import { humanDate } from '@siafoundation/sia-js' +import { routes } from '../../config/routes' +import { EntityHeading } from '../EntityHeading' + +type Props = { + title?: string + transaction: SiaCentralTransaction +} + +export function TransactionHeader({ title, transaction }: Props) { + const { id, timestamp, block_height, confirmations } = transaction + return ( +
+ +
+ {!!timestamp && ( + + + {humanDate(timestamp, { + dateStyle: 'medium', + timeStyle: 'short', + })} + + + )} + {!!block_height && ( + +
+ +
{block_height.toLocaleString()}
+
+
|
+
+ {confirmations >= 72 ? '72+' : confirmations} confirmations +
+
+
+ )} +
+
+ ) +} diff --git a/apps/explorer/components/Transaction/index.tsx b/apps/explorer/components/Transaction/index.tsx new file mode 100644 index 000000000..cd6e67dd3 --- /dev/null +++ b/apps/explorer/components/Transaction/index.tsx @@ -0,0 +1,133 @@ +'use client' + +import { useMemo } from 'react' +import BigNumber from 'bignumber.js' +import { SiaCentralTransaction } from '@siafoundation/sia-central' +import { EntityList, EntityListItemProps } from '@siafoundation/design-system' +import { routes } from '../../config/routes' +import { ContentLayout } from '../ContentLayout' +import { TransactionHeader } from './TransactionHeader' + +type Props = { + transaction: SiaCentralTransaction + title?: string +} + +export function Transaction({ title, transaction }: Props) { + const inputs = useMemo(() => { + if (!transaction) { + return [] + } + const list: EntityListItemProps[] = [] + transaction.siacoin_inputs?.forEach((o) => { + list.push({ + label: + o.source === 'transaction' + ? 'siacoin output' + : o.source.replace(/_/g, ' '), + sc: new BigNumber(o.value), + hash: o.output_id, + }) + }) + transaction.siafund_inputs?.forEach((o) => { + list.push({ + label: 'siafund output', + sc: new BigNumber(o.value), + hash: o.output_id, + }) + }) + return list + }, [transaction]) + + const outputs = useMemo(() => { + if (!transaction) { + return [] + } + const list: EntityListItemProps[] = [] + transaction.siacoin_outputs?.forEach((o) => { + list.push({ + label: + o.source === 'transaction' + ? 'siacoin output' + : o.source.replace(/_/g, ' '), + sc: new BigNumber(o.value), + hash: o.output_id, + }) + }) + transaction.siafund_outputs?.forEach((o) => { + list.push({ + label: 'siafund output', + sf: Number(o.value), + hash: o.output_id, + }) + }) + return list + }, [transaction]) + + const operations = useMemo(() => { + if (!transaction) { + return [] + } + const operations: EntityListItemProps[] = [] + transaction.storage_contracts?.forEach((contract) => { + operations.push({ + label: 'contract formation', + type: 'contract', + href: routes.contract.view.replace(':id', contract.id), + hash: contract.id, + }) + }) + transaction.contract_revisions?.forEach((contract) => { + operations.push({ + label: 'contract revision', + type: 'contract', + href: routes.contract.view.replace(':id', contract.id), + hash: contract.id, + }) + }) + transaction.host_announcements?.forEach((host) => { + operations.push({ + label: 'host announcement', + // type: 'host', + hash: host.net_address, + }) + }) + transaction.storage_proofs?.forEach((proof) => { + operations.push({ + label: 'storage proof', + hash: proof.transaction_id, + }) + }) + return operations + }, [transaction]) + + return ( + + +
+ } + > +
+
+
+ +
+
+ +
+
+ {!!operations?.length && ( + + )} +
+ + ) +} diff --git a/apps/explorer/components/TransactionSkeleton/index.tsx b/apps/explorer/components/TransactionSkeleton/index.tsx new file mode 100644 index 000000000..da035a9ea --- /dev/null +++ b/apps/explorer/components/TransactionSkeleton/index.tsx @@ -0,0 +1,37 @@ +import { Skeleton, EntityList } from '@siafoundation/design-system' +import { ContentLayout } from '../ContentLayout' + +export function TransactionSkeleton() { + return ( + +
+ + +
+
+ } + > +
+
+
+ +
+
+ +
+
+ {/* */} +
+ + ) +} diff --git a/apps/explorer-v1/config/index.ts b/apps/explorer/config/index.ts similarity index 82% rename from apps/explorer-v1/config/index.ts rename to apps/explorer/config/index.ts index 348431fc5..4e3bd7c2c 100644 --- a/apps/explorer-v1/config/index.ts +++ b/apps/explorer/config/index.ts @@ -6,6 +6,5 @@ export const appLink = webLinks.explore.mainnet export const isMainnet = true // APIs -export const navigatorApi = 'https://navigator.sia.tech/navigator-api' export const faucetApi = 'https://navigator.sia.tech/faucet/api' export const siaCentralApi = 'https://api.siacentral.com/v2' diff --git a/apps/explorer/config/routes.ts b/apps/explorer/config/routes.ts new file mode 100644 index 000000000..58447d42a --- /dev/null +++ b/apps/explorer/config/routes.ts @@ -0,0 +1,23 @@ +export const routes = { + home: { + index: '/', + }, + contract: { + view: '/contract/:id', + }, + transaction: { + view: '/tx/:id', + }, + block: { + view: '/block/:id', + }, + address: { + view: '/address/:id', + }, + host: { + view: '/host/:id', + }, + faucet: { + index: '/faucet', + }, +} diff --git a/apps/explorer-v1/config/style.css b/apps/explorer/config/style.css similarity index 100% rename from apps/explorer-v1/config/style.css rename to apps/explorer/config/style.css diff --git a/apps/explorer-v1/config/testnet.ts b/apps/explorer/config/testnet.ts similarity index 100% rename from apps/explorer-v1/config/testnet.ts rename to apps/explorer/config/testnet.ts diff --git a/apps/explorer-v1/hooks/useFaucetFund.ts b/apps/explorer/hooks/useFaucetFund.ts similarity index 100% rename from apps/explorer-v1/hooks/useFaucetFund.ts rename to apps/explorer/hooks/useFaucetFund.ts diff --git a/apps/explorer-v1/hooks/useFaucetStatus.ts b/apps/explorer/hooks/useFaucetStatus.ts similarity index 100% rename from apps/explorer-v1/hooks/useFaucetStatus.ts rename to apps/explorer/hooks/useFaucetStatus.ts diff --git a/apps/explorer-v1/index.d.ts b/apps/explorer/index.d.ts similarity index 100% rename from apps/explorer-v1/index.d.ts rename to apps/explorer/index.d.ts diff --git a/apps/explorer-v1/jest.config.ts b/apps/explorer/jest.config.ts similarity index 75% rename from apps/explorer-v1/jest.config.ts rename to apps/explorer/jest.config.ts index 5f4acc57c..00b4fbc32 100644 --- a/apps/explorer-v1/jest.config.ts +++ b/apps/explorer/jest.config.ts @@ -1,13 +1,13 @@ /* eslint-disable */ export default { - displayName: 'explorer-v1', + displayName: 'explorer', preset: '../../jest.preset.js', transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', - '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], - coverageDirectory: '../../coverage/apps/explorer-v1', + coverageDirectory: '../../coverage/apps/explorer', transformIgnorePatterns: [ '/node_modules/(?!d3|d3-array|d3-axis|d3-brush|d3-chord|d3-color|d3-contour|d3-delaunay|d3-dispatch|d3-drag|d3-dsv|d3-ease|d3-fetch|d3-force|d3-format|d3-geo|d3-hierarchy|d3-interpolate|d3-path|d3-polygon|d3-quadtree|d3-random|d3-scale|d3-scale-chromatic|d3-selection|d3-shape|d3-time|d3-time-format|d3-timer|d3-transition|d3-zoom}|internmap|d3-delaunay|delaunator|robust-predicates)', ], diff --git a/apps/explorer/lib/avatar.ts b/apps/explorer/lib/avatar.ts new file mode 100644 index 000000000..883c0a302 --- /dev/null +++ b/apps/explorer/lib/avatar.ts @@ -0,0 +1,15 @@ +import Identicon from 'identicon.js' + +export function hashToAvatar(hash: string, size = 40) { + const options = { + foreground: [0, 0, 0, 255], + background: [255, 255, 255, 255], + margin: 0.1, + size, + format: 'svg', + } + + const data = new Identicon(hash, options).toString() + + return `data:image/svg+xml;base64,${data}` +} diff --git a/apps/explorer/lib/currency.ts b/apps/explorer/lib/currency.ts new file mode 100644 index 000000000..a7123deca --- /dev/null +++ b/apps/explorer/lib/currency.ts @@ -0,0 +1,6 @@ +import { SiaCentralExchangeRates } from '@siafoundation/sia-central' +import BigNumber from 'bignumber.js' + +export function siacoinToDollars(sc: string, rates: SiaCentralExchangeRates) { + return `$${new BigNumber(sc).div(1e24).times(rates.sc.usd).toFixed(2)}` +} diff --git a/apps/explorer/lib/host.ts b/apps/explorer/lib/host.ts new file mode 100644 index 000000000..172f4b9cf --- /dev/null +++ b/apps/explorer/lib/host.ts @@ -0,0 +1,54 @@ +import { TBToBytes, monthsToBlocks } from '@siafoundation/design-system' +import { + SiaCentralExchangeRates, + SiaCentralHost, +} from '@siafoundation/sia-central' +import BigNumber from 'bignumber.js' +import { humanSiacoin } from '@siafoundation/sia-js' + +type Props = { + host: SiaCentralHost + rates: SiaCentralExchangeRates +} + +export function getStorageCost({ host, rates }: Props) { + return rates + ? `$${new BigNumber(host.settings.storage_price) + .times(TBToBytes(1)) + .times(monthsToBlocks(1)) + .div(1e24) + .times(rates.sc.usd || 1) + .toFixed(2)}/TB` + : `${humanSiacoin( + new BigNumber(host.settings.storage_price) + .times(TBToBytes(1)) + .times(monthsToBlocks(1)), + { fixed: 0 } + )}/TB` +} + +export function getDownloadCost({ host, rates }: Props) { + return rates + ? `$${new BigNumber(host.settings.download_price) + .times(TBToBytes(1)) + .div(1e24) + .times(rates.sc.usd || 1) + .toFixed(2)}/TB` + : `${humanSiacoin( + new BigNumber(host.settings.download_price).times(TBToBytes(1)), + { fixed: 0 } + )}/TB` +} + +export function getUploadCost({ host, rates }: Props) { + return rates + ? `$${new BigNumber(host.settings.upload_price) + .times(TBToBytes(1)) + .div(1e24) + .times(rates.sc.usd || 1) + .toFixed(2)}/TB` + : `${humanSiacoin( + new BigNumber(host.settings.upload_price).times(TBToBytes(1)), + { fixed: 0 } + )}/TB` +} diff --git a/apps/explorer/lib/utils.ts b/apps/explorer/lib/utils.ts new file mode 100644 index 000000000..66a87fdec --- /dev/null +++ b/apps/explorer/lib/utils.ts @@ -0,0 +1,29 @@ +import { EntityType } from '@siafoundation/design-system' +import { routes } from '../config/routes' +import { Metadata } from 'next' +import { appName } from '../config' + +export function getHref(type: EntityType, value: string) { + return routes[type].view.replace(':id', value) +} + +export function buildMetadata({ + title, + description, + url, +}: { + title: string + description: string + url: string +}): Metadata { + return { + title, + description, + openGraph: { + title, + description, + url, + siteName: appName, + }, + } +} diff --git a/apps/explorer-v1/next-env.d.ts b/apps/explorer/next-env.d.ts similarity index 100% rename from apps/explorer-v1/next-env.d.ts rename to apps/explorer/next-env.d.ts diff --git a/apps/explorer-v1/next.config.js b/apps/explorer/next.config.js similarity index 76% rename from apps/explorer-v1/next.config.js rename to apps/explorer/next.config.js index a5c685d26..2bfe767c4 100644 --- a/apps/explorer-v1/next.config.js +++ b/apps/explorer/next.config.js @@ -1,8 +1,8 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires -const withNx = require('@nrwl/next/plugins/with-nx') +const withNx = require('@nx/next/plugins/with-nx') /** - * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} + * @type {import('@nx/next/plugins/with-nx').WithNxOptions} **/ const nextConfig = { // Use downstream webserver compression in production diff --git a/apps/explorer/package.json b/apps/explorer/package.json new file mode 100644 index 000000000..93d6f6140 --- /dev/null +++ b/apps/explorer/package.json @@ -0,0 +1,7 @@ +{ + "name": "explorer", + "description": "The `explorer` user interface, a Sia blockchain explorer interface for Navigator.", + "version": "0.6.0", + "private": true, + "license": "MIT" +} diff --git a/apps/explorer-v1/postcss.config.js b/apps/explorer/postcss.config.js similarity index 100% rename from apps/explorer-v1/postcss.config.js rename to apps/explorer/postcss.config.js diff --git a/apps/explorer-v1/project.json b/apps/explorer/project.json similarity index 54% rename from apps/explorer-v1/project.json rename to apps/explorer/project.json index 70b8a9974..27090f690 100644 --- a/apps/explorer-v1/project.json +++ b/apps/explorer/project.json @@ -1,16 +1,16 @@ { - "name": "explorer-v1", + "name": "explorer", "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/explorer-v1", + "sourceRoot": "apps/explorer", "projectType": "application", "targets": { "build": { - "executor": "@nrwl/next:build", + "executor": "@nx/next:build", "outputs": ["{options.outputPath}"], "defaultConfiguration": "production", "options": { - "root": "apps/explorer-v1", - "outputPath": "dist/apps/explorer-v1", + "outputPath": "dist/apps/explorer", + "postcssConfig": "apps/website/postcss.config.js", "assets": [ { "glob": "**/*", @@ -22,70 +22,70 @@ "configurations": { "development": {}, "development-testnet": { - "outputPath": "dist/apps/explorer-v1-testnet", + "outputPath": "dist/apps/explorer-testnet", "fileReplacements": [ { - "replace": "apps/explorer-v1/config/index.ts", - "with": "apps/explorer-v1/config/testnet.ts" + "replace": "apps/explorer/config/index.ts", + "with": "apps/explorer/config/testnet.ts" } ] }, "production": {}, "production-testnet": { - "outputPath": "dist/apps/explorer-v1-testnet", + "outputPath": "dist/apps/explorer-testnet", "fileReplacements": [ { - "replace": "apps/explorer-v1/config/index.ts", - "with": "apps/explorer-v1/config/testnet.ts" + "replace": "apps/explorer/config/index.ts", + "with": "apps/explorer/config/testnet.ts" } ] }, "ci": { - "outputPath": "apps/explorer-v1/dist" + "outputPath": "apps/explorer/dist" } } }, "serve": { - "executor": "@nrwl/next:server", + "executor": "@nx/next:server", "defaultConfiguration": "development", "options": { - "buildTarget": "explorer-v1:build", + "buildTarget": "explorer:build", "port": 3003 }, "configurations": { "development": { - "buildTarget": "explorer-v1:build:development", + "buildTarget": "explorer:build:development", "dev": true }, "development-testnet": { - "buildTarget": "explorer-v1:build:development-testnet", + "buildTarget": "explorer:build:development-testnet", "dev": true, "port": 3005 }, "production": { - "buildTarget": "explorer-v1:build:production", + "buildTarget": "explorer:build:production", "dev": false }, "production-testnet": { - "buildTarget": "explorer-v1:build:production-testnet", + "buildTarget": "explorer:build:production-testnet", "dev": false, "port": 3005 } } }, "test": { - "executor": "@nrwl/jest:jest", - "outputs": ["{workspaceRoot}/coverage/apps/explorer-v1"], + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/apps/explorer"], "options": { - "jestConfig": "apps/explorer-v1/jest.config.ts", + "jestConfig": "apps/explorer/jest.config.ts", "passWithNoTests": true } }, "lint": { - "executor": "@nrwl/linter:eslint", + "executor": "@nx/linter:eslint", "outputs": ["{options.outputFile}"], "options": { - "lintFilePatterns": ["apps/explorer-v1/**/*.{ts,tsx,js,jsx}"] + "lintFilePatterns": ["apps/explorer/**/*.{ts,tsx,js,jsx}"] } } }, diff --git a/apps/explorer-v1/specs/index.spec.tsx b/apps/explorer/specs/index.spec.tsx similarity index 100% rename from apps/explorer-v1/specs/index.spec.tsx rename to apps/explorer/specs/index.spec.tsx diff --git a/apps/explorer-v1/tailwind.config.js b/apps/explorer/tailwind.config.js similarity index 79% rename from apps/explorer-v1/tailwind.config.js rename to apps/explorer/tailwind.config.js index 681e9d96c..f927defea 100644 --- a/apps/explorer-v1/tailwind.config.js +++ b/apps/explorer/tailwind.config.js @@ -1,4 +1,4 @@ -const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind') +const { createGlobPatternsForDependencies } = require('@nx/react/tailwind') const { join } = require('path') module.exports = { diff --git a/apps/explorer/tsconfig.json b/apps/explorer/tsconfig.json new file mode 100644 index 000000000..8d56a092d --- /dev/null +++ b/apps/explorer/tsconfig.json @@ -0,0 +1,38 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "preserve", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "resolveJsonModule": true, + "isolatedModules": true, + "incremental": true, + "types": [ + "jest", + "node" + ], + "plugins": [ + { + "name": "next" + } + ], + "strictNullChecks": true + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "next-env.d.ts", + "../../dist/apps/explorer/.next/types/**/*.ts", + "../../dist/apps/explorer-testnet/.next/types/**/*.ts" + ], + "exclude": [ + "node_modules", + "jest.config.ts" + ] +} diff --git a/apps/explorer-v1/tsconfig.spec.json b/apps/explorer/tsconfig.spec.json similarity index 100% rename from apps/explorer-v1/tsconfig.spec.json rename to apps/explorer/tsconfig.spec.json diff --git a/apps/hostd/.eslintrc.json b/apps/hostd/.eslintrc.json index 806f27be7..6c0ac5de2 100644 --- a/apps/hostd/.eslintrc.json +++ b/apps/hostd/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": [ - "plugin:@nrwl/nx/react-typescript", + "plugin:@nx/react-typescript", "../../.eslintrc.json", "next", "next/core-web-vitals" diff --git a/apps/hostd/jest.config.ts b/apps/hostd/jest.config.ts index 0deff587e..ecb7018f3 100644 --- a/apps/hostd/jest.config.ts +++ b/apps/hostd/jest.config.ts @@ -3,8 +3,8 @@ export default { displayName: 'hostd', preset: '../../jest.preset.js', transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', - '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/apps/hostd', diff --git a/apps/hostd/next.config.js b/apps/hostd/next.config.js index c471b81ee..fe05faa24 100644 --- a/apps/hostd/next.config.js +++ b/apps/hostd/next.config.js @@ -1,8 +1,8 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires -const withNx = require('@nrwl/next/plugins/with-nx') +const withNx = require('@nx/next/plugins/with-nx') /** - * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} + * @type {import('@nx/next/plugins/with-nx').WithNxOptions} **/ const nextConfig = { // Use downstream webserver compression in production diff --git a/apps/hostd/project.json b/apps/hostd/project.json index 44ba72d72..394eccbb4 100644 --- a/apps/hostd/project.json +++ b/apps/hostd/project.json @@ -5,11 +5,12 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/next:build", - "outputs": ["{options.outputPath}"], + "executor": "@nx/next:build", + "outputs": [ + "{options.outputPath}" + ], "defaultConfiguration": "production", "options": { - "root": "apps/hostd", "outputPath": "dist/apps/hostd", "postcssConfig": "apps/hostd/postcss.config.js", "assets": [ @@ -38,7 +39,7 @@ } }, "serve": { - "executor": "@nrwl/next:server", + "executor": "@nx/next:server", "defaultConfiguration": "development", "options": { "buildTarget": "hostd:build", @@ -60,24 +61,30 @@ } }, "export": { - "executor": "@nrwl/next:export", + "executor": "@nx/next:export", "options": { "buildTarget": "hostd:build:embed" } }, "test": { - "executor": "@nrwl/jest:jest", - "outputs": ["{workspaceRoot}/coverage/apps/hostd"], + "executor": "@nx/jest:jest", + "outputs": [ + "{workspaceRoot}/coverage/apps/hostd" + ], "options": { "jestConfig": "apps/hostd/jest.config.ts", "passWithNoTests": true } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], + "executor": "@nx/linter:eslint", + "outputs": [ + "{options.outputFile}" + ], "options": { - "lintFilePatterns": ["apps/hostd/**/*.{ts,tsx,js,jsx}"] + "lintFilePatterns": [ + "apps/hostd/**/*.{ts,tsx,js,jsx}" + ] } } }, diff --git a/apps/hostd/tailwind.config.js b/apps/hostd/tailwind.config.js index 4c67e5692..df5932795 100644 --- a/apps/hostd/tailwind.config.js +++ b/apps/hostd/tailwind.config.js @@ -1,4 +1,4 @@ -const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind') +const { createGlobPatternsForDependencies } = require('@nx/react/tailwind') const { join } = require('path') module.exports = { diff --git a/apps/renterd/.eslintrc.json b/apps/renterd/.eslintrc.json index 77e2c2d14..ddc4f4b04 100644 --- a/apps/renterd/.eslintrc.json +++ b/apps/renterd/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": [ - "plugin:@nrwl/nx/react-typescript", + "plugin:@nx/react-typescript", "../../.eslintrc.json", "next", "next/core-web-vitals" diff --git a/apps/renterd/components/Config/index.tsx b/apps/renterd/components/Config/index.tsx index 39c8eb45e..f79964f2f 100644 --- a/apps/renterd/components/Config/index.tsx +++ b/apps/renterd/components/Config/index.tsx @@ -34,7 +34,6 @@ import { transformUpUploadPacking, } from './transform' import { useForm } from 'react-hook-form' -import { useSiaCentralHostsNetworkAverages } from '@siafoundation/react-core' import { toSiacoins } from '@siafoundation/sia-js' import { useContractSetSettings } from '../../hooks/useContractSetSettings' import { useGougingSettings } from '../../hooks/useGougingSettings' @@ -45,6 +44,7 @@ import { useConfigDisplayOptions, } from '../../hooks/useConfigDisplayOptions' import { useUploadPackingSettings } from '../../hooks/useUploadPackingSettings' +import { useSiaCentralHostsNetworkAverages } from '@siafoundation/react-sia-central' export function Config() { const { openDialog } = useDialog() diff --git a/apps/renterd/components/Hosts/HostMap/Globe.tsx b/apps/renterd/components/Hosts/HostMap/Globe.tsx index b4bfda2f2..74cb24bda 100644 --- a/apps/renterd/components/Hosts/HostMap/Globe.tsx +++ b/apps/renterd/components/Hosts/HostMap/Globe.tsx @@ -2,16 +2,14 @@ import { useEffect, useRef, useCallback, useMemo } from 'react' import { GlobeMethods } from 'react-globe.gl' import { getHostLabel } from './utils' import { useElementSize } from 'usehooks-ts' -import { - useSiaCentralMarketExchangeRate, - useTryUntil, -} from '@siafoundation/react-core' +import { useTryUntil } from '@siafoundation/react-core' import earthDarkContrast from '../../../assets/earth-dark-contrast.png' import earthTopology from '../../../assets/earth-topology.png' import { GlobeDyn } from './GlobeDyn' import { HostDataWithLocation } from '../../../contexts/hosts/types' import BigNumber from 'bignumber.js' import { getHostStatus } from '../../../contexts/hosts/status' +import { useSiaCentralMarketExchangeRate } from '@siafoundation/react-sia-central' export type Commands = { moveToLocation: ( diff --git a/apps/renterd/contexts/contracts/index.tsx b/apps/renterd/contexts/contracts/index.tsx index 3e2f56243..793fed133 100644 --- a/apps/renterd/contexts/contracts/index.tsx +++ b/apps/renterd/contexts/contracts/index.tsx @@ -13,7 +13,6 @@ import { useEstimatedNetworkBlockHeight, } from '@siafoundation/react-renterd' import { createContext, useContext, useMemo } from 'react' -import { useSiaCentralHosts } from '@siafoundation/react-core' import BigNumber from 'bignumber.js' import { ContractData, @@ -22,6 +21,7 @@ import { sortOptions, } from './types' import { columns } from './columns' +import { useSiaCentralHosts } from '@siafoundation/react-sia-central' const defaultLimit = 50 diff --git a/apps/renterd/contexts/hosts/index.tsx b/apps/renterd/contexts/hosts/index.tsx index b37428408..b52472d5b 100644 --- a/apps/renterd/contexts/hosts/index.tsx +++ b/apps/renterd/contexts/hosts/index.tsx @@ -33,8 +33,9 @@ import { columns } from './columns' import { useContracts } from '../contracts' import { useDataset } from './dataset' import { useApp } from '../app' -import { useAppSettings, useSiaCentralHosts } from '@siafoundation/react-core' +import { useAppSettings } from '@siafoundation/react-core' import { Commands, emptyCommands } from '../../components/Hosts/HostMap/Globe' +import { useSiaCentralHosts } from '@siafoundation/react-sia-central' const defaultLimit = 50 diff --git a/apps/renterd/jest.config.ts b/apps/renterd/jest.config.ts index 04662277a..f9eec2977 100644 --- a/apps/renterd/jest.config.ts +++ b/apps/renterd/jest.config.ts @@ -3,8 +3,8 @@ export default { displayName: 'renterd', preset: '../../jest.preset.js', transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', - '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/apps/renterd', diff --git a/apps/renterd/next.config.js b/apps/renterd/next.config.js index c471b81ee..fe05faa24 100644 --- a/apps/renterd/next.config.js +++ b/apps/renterd/next.config.js @@ -1,8 +1,8 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires -const withNx = require('@nrwl/next/plugins/with-nx') +const withNx = require('@nx/next/plugins/with-nx') /** - * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} + * @type {import('@nx/next/plugins/with-nx').WithNxOptions} **/ const nextConfig = { // Use downstream webserver compression in production diff --git a/apps/renterd/project.json b/apps/renterd/project.json index 76cf48f05..fa643f2af 100644 --- a/apps/renterd/project.json +++ b/apps/renterd/project.json @@ -5,11 +5,12 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/next:build", - "outputs": ["{options.outputPath}"], + "executor": "@nx/next:build", + "outputs": [ + "{options.outputPath}" + ], "defaultConfiguration": "production", "options": { - "root": "apps/renterd", "outputPath": "dist/apps/renterd", "postcssConfig": "apps/renterd/postcss.config.js", "assets": [ @@ -38,7 +39,7 @@ } }, "serve": { - "executor": "@nrwl/next:server", + "executor": "@nx/next:server", "defaultConfiguration": "development", "options": { "buildTarget": "renterd:build", @@ -60,24 +61,30 @@ } }, "export": { - "executor": "@nrwl/next:export", + "executor": "@nx/next:export", "options": { "buildTarget": "renterd:build:embed" } }, "test": { - "executor": "@nrwl/jest:jest", - "outputs": ["{workspaceRoot}/coverage/apps/renterd"], + "executor": "@nx/jest:jest", + "outputs": [ + "{workspaceRoot}/coverage/apps/renterd" + ], "options": { "jestConfig": "apps/renterd/jest.config.ts", "passWithNoTests": true } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], + "executor": "@nx/linter:eslint", + "outputs": [ + "{options.outputFile}" + ], "options": { - "lintFilePatterns": ["apps/renterd/**/*.{ts,tsx,js,jsx}"] + "lintFilePatterns": [ + "apps/renterd/**/*.{ts,tsx,js,jsx}" + ] } } }, diff --git a/apps/renterd/tailwind.config.js b/apps/renterd/tailwind.config.js index 9be0784e7..bbe9271ad 100644 --- a/apps/renterd/tailwind.config.js +++ b/apps/renterd/tailwind.config.js @@ -1,4 +1,4 @@ -const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind') +const { createGlobPatternsForDependencies } = require('@nx/react/tailwind') const { join } = require('path') module.exports = { diff --git a/apps/walletd/.eslintrc.json b/apps/walletd/.eslintrc.json index 341a4462e..fa4b119d5 100644 --- a/apps/walletd/.eslintrc.json +++ b/apps/walletd/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": [ - "plugin:@nrwl/nx/react-typescript", + "plugin:@nx/react-typescript", "../../.eslintrc.json", "next", "next/core-web-vitals" diff --git a/apps/walletd/jest.config.ts b/apps/walletd/jest.config.ts index a13458383..cd4e015e6 100644 --- a/apps/walletd/jest.config.ts +++ b/apps/walletd/jest.config.ts @@ -3,8 +3,8 @@ export default { displayName: 'walletd', preset: '../../jest.preset.js', transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', - '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/apps/walletd', diff --git a/apps/walletd/next.config.js b/apps/walletd/next.config.js index c471b81ee..fe05faa24 100644 --- a/apps/walletd/next.config.js +++ b/apps/walletd/next.config.js @@ -1,8 +1,8 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires -const withNx = require('@nrwl/next/plugins/with-nx') +const withNx = require('@nx/next/plugins/with-nx') /** - * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} + * @type {import('@nx/next/plugins/with-nx').WithNxOptions} **/ const nextConfig = { // Use downstream webserver compression in production diff --git a/apps/walletd/project.json b/apps/walletd/project.json index d7a40f2ca..1207b73f8 100644 --- a/apps/walletd/project.json +++ b/apps/walletd/project.json @@ -5,11 +5,12 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/next:build", - "outputs": ["{options.outputPath}"], + "executor": "@nx/next:build", + "outputs": [ + "{options.outputPath}" + ], "defaultConfiguration": "production", "options": { - "root": "apps/walletd", "outputPath": "dist/apps/walletd", "assets": [ { @@ -37,7 +38,7 @@ } }, "serve": { - "executor": "@nrwl/next:server", + "executor": "@nx/next:server", "defaultConfiguration": "development", "options": { "buildTarget": "walletd:build", @@ -59,24 +60,30 @@ } }, "export": { - "executor": "@nrwl/next:export", + "executor": "@nx/next:export", "options": { "buildTarget": "walletd:build:embed" } }, "test": { - "executor": "@nrwl/jest:jest", - "outputs": ["{workspaceRoot}/coverage/apps/walletd"], + "executor": "@nx/jest:jest", + "outputs": [ + "{workspaceRoot}/coverage/apps/walletd" + ], "options": { "jestConfig": "apps/walletd/jest.config.ts", "passWithNoTests": true } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], + "executor": "@nx/linter:eslint", + "outputs": [ + "{options.outputFile}" + ], "options": { - "lintFilePatterns": ["apps/walletd/**/*.{ts,tsx,js,jsx}"] + "lintFilePatterns": [ + "apps/walletd/**/*.{ts,tsx,js,jsx}" + ] } } }, diff --git a/apps/walletd/public/walletd.wasm b/apps/walletd/public/walletd.wasm index 88a16c803..76e2fb986 100755 Binary files a/apps/walletd/public/walletd.wasm and b/apps/walletd/public/walletd.wasm differ diff --git a/apps/walletd/tailwind.config.js b/apps/walletd/tailwind.config.js index c1bfed96a..4b369f17e 100644 --- a/apps/walletd/tailwind.config.js +++ b/apps/walletd/tailwind.config.js @@ -1,4 +1,4 @@ -const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind') +const { createGlobPatternsForDependencies } = require('@nx/react/tailwind') const { join } = require('path') module.exports = { diff --git a/apps/website/.eslintrc.json b/apps/website/.eslintrc.json index c8ab71054..251a17744 100644 --- a/apps/website/.eslintrc.json +++ b/apps/website/.eslintrc.json @@ -1,6 +1,6 @@ { "extends": [ - "plugin:@nrwl/nx/react-typescript", + "plugin:@nx/react-typescript", "../../.eslintrc.json", "next", "next/core-web-vitals" diff --git a/apps/website/jest.config.ts b/apps/website/jest.config.ts index 3c1be9fd5..fcd35b97e 100644 --- a/apps/website/jest.config.ts +++ b/apps/website/jest.config.ts @@ -2,8 +2,8 @@ export default { displayName: 'website', transform: { - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', - '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/apps/website', diff --git a/apps/website/next.config.js b/apps/website/next.config.js index adc94a4bc..e08c83995 100644 --- a/apps/website/next.config.js +++ b/apps/website/next.config.js @@ -1,5 +1,5 @@ const Dotenv = require('dotenv-webpack') -const withNx = require('@nrwl/next/plugins/with-nx') +const { composePlugins, withNx } = require('@nx/next') // MDX support const withMDX = require('@next/mdx')({ @@ -7,7 +7,7 @@ const withMDX = require('@next/mdx')({ }) /** - * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} + * @type {import('@nx/next/plugins/with-nx').WithNxOptions} **/ const nextConfig = { // Use downstream webserver compression in production @@ -43,4 +43,6 @@ const nextConfig = { }, } -module.exports = withMDX(withNx(nextConfig)) +const plugins = [withMDX, withNx] + +module.exports = composePlugins(...plugins)(nextConfig) diff --git a/apps/website/project.json b/apps/website/project.json index 3376bb18d..28c410549 100644 --- a/apps/website/project.json +++ b/apps/website/project.json @@ -5,11 +5,12 @@ "projectType": "application", "targets": { "build": { - "executor": "@nrwl/next:build", - "outputs": ["{options.outputPath}"], + "executor": "@nx/next:build", + "outputs": [ + "{options.outputPath}" + ], "defaultConfiguration": "production", "options": { - "root": "apps/website", "outputPath": "dist/apps/website", "postcssConfig": "apps/website/postcss.config.js", "assets": [ @@ -29,7 +30,7 @@ } }, "serve": { - "executor": "@nrwl/next:server", + "executor": "@nx/next:server", "defaultConfiguration": "development", "options": { "buildTarget": "website:build", @@ -49,18 +50,24 @@ } }, "test": { - "executor": "@nrwl/jest:jest", - "outputs": ["{workspaceRoot}/coverage/apps/website"], + "executor": "@nx/jest:jest", + "outputs": [ + "{workspaceRoot}/coverage/apps/website" + ], "options": { "jestConfig": "apps/website/jest.config.ts", "passWithNoTests": true } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": ["{options.outputFile}"], + "executor": "@nx/linter:eslint", + "outputs": [ + "{options.outputFile}" + ], "options": { - "lintFilePatterns": ["apps/website/**/*.{ts,tsx,js,jsx}"] + "lintFilePatterns": [ + "apps/website/**/*.{ts,tsx,js,jsx}" + ] } } }, diff --git a/apps/website/tailwind.config.js b/apps/website/tailwind.config.js index 22c26a966..2b797bc76 100644 --- a/apps/website/tailwind.config.js +++ b/apps/website/tailwind.config.js @@ -1,4 +1,4 @@ -const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind') +const { createGlobPatternsForDependencies } = require('@nx/react/tailwind') const { join } = require('path') module.exports = { diff --git a/jest.config.ts b/jest.config.ts index 0bdbba214..1666f7c14 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,4 +1,4 @@ -const { getJestProjects } = require('@nrwl/jest') +const { getJestProjects } = require('@nx/jest') export default { projects: getJestProjects(), diff --git a/jest.preset.js b/jest.preset.js index f9041a536..3d449d567 100644 --- a/jest.preset.js +++ b/jest.preset.js @@ -1,3 +1,15 @@ -const nxPreset = require('@nrwl/jest/preset').default; +const nxPreset = require('@nx/jest/preset').default -module.exports = { ...nxPreset } +module.exports = { + ...nxPreset, + /* TODO: Update to latest Jest snapshotFormat + * By default Nx has kept the older style of Jest Snapshot formats + * to prevent breaking of any existing tests with snapshots. + * It's recommend you update to the latest format. + * You can do this by removing snapshotFormat property + * and running tests with --update-snapshot flag. + * Example: "nx affected --targets=test --update-snapshot" + * More info: https://jestjs.io/docs/upgrading-to-jest29#snapshot-format + */ + snapshotFormat: { escapeString: true, printBasicPrototype: true }, +} diff --git a/libs/data-sources/.babelrc b/libs/data-sources/.babelrc index b63f0528f..fd4cbcdef 100644 --- a/libs/data-sources/.babelrc +++ b/libs/data-sources/.babelrc @@ -1,7 +1,7 @@ { "presets": [ [ - "@nrwl/js/babel", + "@nx/js/babel", { "useBuiltIns": "usage" } diff --git a/libs/data-sources/jest.config.ts b/libs/data-sources/jest.config.ts index 61747c886..92a86e154 100644 --- a/libs/data-sources/jest.config.ts +++ b/libs/data-sources/jest.config.ts @@ -2,14 +2,15 @@ export default { displayName: 'data-sources', - globals: { - 'ts-jest': { - tsconfig: '/tsconfig.spec.json', - }, - }, + globals: {}, testEnvironment: 'node', transform: { - '^.+\\.[tj]sx?$': 'ts-jest', + '^.+\\.[tj]sx?$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/libs/data-sources', diff --git a/libs/data-sources/project.json b/libs/data-sources/project.json index fcc9fa1ea..e412b5647 100644 --- a/libs/data-sources/project.json +++ b/libs/data-sources/project.json @@ -5,23 +5,24 @@ "projectType": "library", "targets": { "build": { - "executor": "@nrwl/js:tsc", + "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/libs/data-sources", "main": "libs/data-sources/src/index.ts", - "tsConfig": "libs/data-sources/tsconfig.lib.json" + "tsConfig": "libs/data-sources/tsconfig.lib.json", + "updateBuildableProjectDepsInPackageJson": true } }, "lint": { - "executor": "@nrwl/linter:eslint", + "executor": "@nx/linter:eslint", "outputs": ["{options.outputFile}"], "options": { "lintFilePatterns": ["libs/data-sources/**/*.ts"] } }, "test": { - "executor": "@nrwl/jest:jest", + "executor": "@nx/jest:jest", "outputs": ["{workspaceRoot}/coverage/libs/data-sources"], "options": { "jestConfig": "libs/data-sources/jest.config.ts", diff --git a/libs/design-system/.babelrc b/libs/design-system/.babelrc index ccae900be..1ea870ead 100644 --- a/libs/design-system/.babelrc +++ b/libs/design-system/.babelrc @@ -1,7 +1,7 @@ { "presets": [ [ - "@nrwl/react/babel", + "@nx/react/babel", { "runtime": "automatic", "useBuiltIns": "usage" diff --git a/libs/design-system/.eslintrc.json b/libs/design-system/.eslintrc.json index 734ddacee..a39ac5d05 100644 --- a/libs/design-system/.eslintrc.json +++ b/libs/design-system/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "extends": ["plugin:@nx/react", "../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { diff --git a/libs/design-system/jest.config.ts b/libs/design-system/jest.config.ts index f313c9303..82efd1817 100644 --- a/libs/design-system/jest.config.ts +++ b/libs/design-system/jest.config.ts @@ -2,7 +2,7 @@ export default { displayName: 'design-system', transform: { - '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/react/babel'] }], + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], coverageDirectory: '../../coverage/libs/design-system', diff --git a/libs/design-system/package.json b/libs/design-system/package.json index 6d4f5844e..16a1511a2 100644 --- a/libs/design-system/package.json +++ b/libs/design-system/package.json @@ -3,4 +3,4 @@ "description": "React-based design system used across Sia apps and websites", "version": "0.57.0", "license": "MIT" -} \ No newline at end of file +} diff --git a/libs/design-system/project.json b/libs/design-system/project.json index eb594d600..07d0571a3 100644 --- a/libs/design-system/project.json +++ b/libs/design-system/project.json @@ -7,18 +7,14 @@ "targets": { "build": { "executor": "@nrwl/rollup:rollup", - "outputs": [ - "{options.outputPath}" - ], + "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/libs/design-system", "tsConfig": "libs/design-system/tsconfig.lib.json", "project": "libs/design-system/package.json", "entryFile": "libs/design-system/src/index.ts", - "external": [ - "react/jsx-runtime" - ], - "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nx/react/plugins/bundle-rollup", "compiler": "swc", "buildableProjectDepsInPackageJsonType": "peerDependencies", "assets": [ @@ -41,21 +37,15 @@ } }, "lint": { - "executor": "@nrwl/linter:eslint", - "outputs": [ - "{options.outputFile}" - ], + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], "options": { - "lintFilePatterns": [ - "libs/design-system/**/*.{ts,tsx,js,jsx}" - ] + "lintFilePatterns": ["libs/design-system/**/*.{ts,tsx,js,jsx}"] } }, "test": { - "executor": "@nrwl/jest:jest", - "outputs": [ - "{workspaceRoot}/coverage/libs/design-system" - ], + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/libs/design-system"], "options": { "jestConfig": "libs/design-system/jest.config.ts", "passWithNoTests": true diff --git a/libs/design-system/src/app/AppAuthedLayout/SidenavItem.tsx b/libs/design-system/src/app/AppAuthedLayout/SidenavItem.tsx index 0d62973a5..9dd8c262d 100644 --- a/libs/design-system/src/app/AppAuthedLayout/SidenavItem.tsx +++ b/libs/design-system/src/app/AppAuthedLayout/SidenavItem.tsx @@ -1,7 +1,9 @@ +'use client' + import { Button } from '../../core/Button' import { Link } from '../../core/Link' import { Tooltip } from '../../core/Tooltip' -import { useRouter } from 'next/router' +import { usePathname } from 'next/navigation' type Props = { title: string @@ -11,12 +13,9 @@ type Props = { } export function SidenavItem({ title, children, route, onClick }: Props) { - const router = useRouter() + const pathname = usePathname() const state = - route && - (route === '/' - ? router.pathname === route - : router.pathname.startsWith(route)) + route && (route === '/' ? pathname === route : pathname.startsWith(route)) if (!route) { return ( diff --git a/libs/design-system/src/app/AppAuthedLayout/index.tsx b/libs/design-system/src/app/AppAuthedLayout/index.tsx index 2c293ba28..28f1215d0 100644 --- a/libs/design-system/src/app/AppAuthedLayout/index.tsx +++ b/libs/design-system/src/app/AppAuthedLayout/index.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import { useAppSettings } from '@siafoundation/react-core' import { Container } from '../../core/Container' diff --git a/libs/design-system/src/app/AppAuthedLayout/useConnAndPassLock.ts b/libs/design-system/src/app/AppAuthedLayout/useConnAndPassLock.ts index 7be86b8e1..7b5bef7bd 100644 --- a/libs/design-system/src/app/AppAuthedLayout/useConnAndPassLock.ts +++ b/libs/design-system/src/app/AppAuthedLayout/useConnAndPassLock.ts @@ -1,3 +1,5 @@ +'use client' + import { useAppSettings } from '@siafoundation/react-core' import { NextRouter, useRouter } from 'next/router' import { useEffect } from 'react' diff --git a/libs/design-system/src/app/AppLogin.tsx b/libs/design-system/src/app/AppLogin.tsx index dc1f814dd..b7d0ab6a7 100644 --- a/libs/design-system/src/app/AppLogin.tsx +++ b/libs/design-system/src/app/AppLogin.tsx @@ -1,3 +1,5 @@ +'use client' + import { useAppSettings } from '@siafoundation/react-core' import { useRouter } from 'next/router' import axios, { AxiosError } from 'axios' diff --git a/libs/design-system/src/app/ConfirmDialog.tsx b/libs/design-system/src/app/ConfirmDialog.tsx index 941669697..286b0d74d 100644 --- a/libs/design-system/src/app/ConfirmDialog.tsx +++ b/libs/design-system/src/app/ConfirmDialog.tsx @@ -1,3 +1,5 @@ +'use client' + import { Dialog } from '../core/Dialog' import { FormSubmitButton } from '../components/Form' import { useCallback } from 'react' diff --git a/libs/design-system/src/app/DatumCardConfigurable.tsx b/libs/design-system/src/app/DatumCardConfigurable.tsx index ea4a71776..7be911047 100644 --- a/libs/design-system/src/app/DatumCardConfigurable.tsx +++ b/libs/design-system/src/app/DatumCardConfigurable.tsx @@ -1,3 +1,5 @@ +'use client' + import { Option, Select } from '../core/Select' import { Text } from '../core/Text' import { DatumCard } from '../components/DatumCard' diff --git a/libs/design-system/src/app/SyncerConnectPeerDialog.tsx b/libs/design-system/src/app/SyncerConnectPeerDialog.tsx index 19e50c80e..ad194dcd8 100644 --- a/libs/design-system/src/app/SyncerConnectPeerDialog.tsx +++ b/libs/design-system/src/app/SyncerConnectPeerDialog.tsx @@ -1,3 +1,5 @@ +'use client' + import { Paragraph } from '../core/Paragraph' import { triggerToast } from '../lib/toast' import { diff --git a/libs/design-system/src/app/WalletAddAddressDialog.tsx b/libs/design-system/src/app/WalletAddAddressDialog.tsx index e8aa5ee72..7a6d407f1 100644 --- a/libs/design-system/src/app/WalletAddAddressDialog.tsx +++ b/libs/design-system/src/app/WalletAddAddressDialog.tsx @@ -1,3 +1,5 @@ +'use client' + import { Paragraph } from '../core/Paragraph' import { TextField } from '../core/TextField' import { Button } from '../core/Button' diff --git a/libs/design-system/src/app/WalletBalanceEvolution.tsx b/libs/design-system/src/app/WalletBalanceEvolution.tsx index affc8f8c9..30339eb76 100644 --- a/libs/design-system/src/app/WalletBalanceEvolution.tsx +++ b/libs/design-system/src/app/WalletBalanceEvolution.tsx @@ -1,3 +1,5 @@ +'use client' + import { useMemo } from 'react' import { ChartXY, Chart } from '../components/ChartXY' import { humanSiacoin } from '@siafoundation/sia-js' diff --git a/libs/design-system/src/app/WalletSendSiacoinDialog/index.tsx b/libs/design-system/src/app/WalletSendSiacoinDialog/index.tsx index 01e0a34c4..73ab43c5f 100644 --- a/libs/design-system/src/app/WalletSendSiacoinDialog/index.tsx +++ b/libs/design-system/src/app/WalletSendSiacoinDialog/index.tsx @@ -1,3 +1,5 @@ +'use client' + import BigNumber from 'bignumber.js' import { useMemo, useState } from 'react' import { toHastings } from '@siafoundation/sia-js' diff --git a/libs/design-system/src/components/BlockList.tsx b/libs/design-system/src/components/BlockList.tsx index 5305fb1d6..6480dd67e 100644 --- a/libs/design-system/src/components/BlockList.tsx +++ b/libs/design-system/src/components/BlockList.tsx @@ -2,16 +2,16 @@ import { Panel } from '../core/Panel' import { Heading } from '../core/Heading' import { Link } from '../core/Link' import { Text } from '../core/Text' -import { getEntityTypeInitials, getEntityTypeLabel } from '../lib/entityTypes' +import { getEntityTypeLabel } from '../lib/entityTypes' import { humanNumber } from '@siafoundation/sia-js' import { formatDistance } from 'date-fns' import { EntityAvatar } from './EntityAvatar' -import { EntityListSkeleton, itemBorderStyles } from './EntityList' import { cx } from 'class-variance-authority' +import { EntityListSkeleton } from './EntityListSkeleton' type BlockListItemProps = { - miningPool: string - timestamp: number + miningPool?: string + timestamp: string | number height: number href: string } @@ -51,7 +51,7 @@ export function BlockList({ title, blocks }: Props) { > @@ -61,8 +61,14 @@ export function BlockList({ title, blocks }: Props) { {humanNumber(block.height)} - {' '} - mined by {block.miningPool}{' '} + + {block.miningPool + ? ' mined by ' + : i < blocks.length - 1 + ? ' mined ' + : ''} + {block.miningPool} + {block.miningPool ? ' ' : ''} {i < blocks.length - 1 ? `in ${formatDistance( new Date(block.timestamp), @@ -83,3 +89,10 @@ export function BlockList({ title, blocks }: Props) { ) } + +function itemBorderStyles() { + return cx( + 'border-t border-gray-200 dark:border-graydark-300', + 'first:border-none' + ) +} diff --git a/libs/design-system/src/components/ChartBrush/index.tsx b/libs/design-system/src/components/ChartBrush/index.tsx index 6c8343ee6..6aca9a337 100644 --- a/libs/design-system/src/components/ChartBrush/index.tsx +++ b/libs/design-system/src/components/ChartBrush/index.tsx @@ -1,3 +1,5 @@ +'use client' + import { MutableRefObject, useMemo } from 'react' import { scaleTime, scaleLinear } from '@visx/scale' import { PatternLines } from '@visx/pattern' diff --git a/libs/design-system/src/components/ChartTimeValue/AreaChart.tsx b/libs/design-system/src/components/ChartTimeValue/AreaChart.tsx index ff6185927..3e9f0ec87 100644 --- a/libs/design-system/src/components/ChartTimeValue/AreaChart.tsx +++ b/libs/design-system/src/components/ChartTimeValue/AreaChart.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import { AreaClosed } from '@visx/shape' import { Group } from '@visx/group' diff --git a/libs/design-system/src/components/ChartTimeValue/index.tsx b/libs/design-system/src/components/ChartTimeValue/index.tsx index f7d3b8e21..a8d6f5728 100644 --- a/libs/design-system/src/components/ChartTimeValue/index.tsx +++ b/libs/design-system/src/components/ChartTimeValue/index.tsx @@ -1,3 +1,5 @@ +'use client' + import React, { useMemo, useCallback, useState, useRef, useEffect } from 'react' import { Line, Bar } from '@visx/shape' import { GridColumns, GridRows } from '@visx/grid' @@ -153,7 +155,7 @@ const Chart = withTooltip( if (selectedDataset.dataset.length > 1) { return selectedDataset.dataset } - const paddedDataset = [] + const paddedDataset: Point[] = [] const startTime = selectedDataset.dataset.length ? selectedDataset.dataset[0].timestamp : new Date().getTime() diff --git a/libs/design-system/src/components/ChartXY/ChartXYGraph.tsx b/libs/design-system/src/components/ChartXY/ChartXYGraph.tsx index 6dc92ddfa..b975c093d 100644 --- a/libs/design-system/src/components/ChartXY/ChartXYGraph.tsx +++ b/libs/design-system/src/components/ChartXY/ChartXYGraph.tsx @@ -1,3 +1,5 @@ +'use client' + import { Fragment } from 'react' import { LinearGradient } from '@visx/gradient' import { Text } from '../../core/Text' diff --git a/libs/design-system/src/components/ChartXY/getChartComponents.ts b/libs/design-system/src/components/ChartXY/getChartComponents.ts index 06fc197a0..4f8d1d788 100644 --- a/libs/design-system/src/components/ChartXY/getChartComponents.ts +++ b/libs/design-system/src/components/ChartXY/getChartComponents.ts @@ -1,3 +1,5 @@ +'use client' + import { // animated AnimatedAnnotation, diff --git a/libs/design-system/src/components/ChartXY/index.tsx b/libs/design-system/src/components/ChartXY/index.tsx index ced7b3a30..430266cf9 100644 --- a/libs/design-system/src/components/ChartXY/index.tsx +++ b/libs/design-system/src/components/ChartXY/index.tsx @@ -1,3 +1,5 @@ +'use client' + import ParentSize from '@visx/responsive/lib/components/ParentSize' import { Panel } from '../../core/Panel' import { ChartXYGraph } from './ChartXYGraph' diff --git a/libs/design-system/src/components/ChartXY/useChartXY.tsx b/libs/design-system/src/components/ChartXY/useChartXY.tsx index 94f2baa57..01ffa9956 100644 --- a/libs/design-system/src/components/ChartXY/useChartXY.tsx +++ b/libs/design-system/src/components/ChartXY/useChartXY.tsx @@ -1,3 +1,5 @@ +'use client' + import { useCallback, useMemo, useState } from 'react' import { AnimationTrajectory } from '@visx/react-spring/lib/types' import { GlyphCross, GlyphDot, GlyphStar } from '@visx/glyph' diff --git a/libs/design-system/src/components/Datum.tsx b/libs/design-system/src/components/Datum.tsx deleted file mode 100644 index 30989b844..000000000 --- a/libs/design-system/src/components/Datum.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { Text } from '../core/Text' -import { ValueSf } from '../components/ValueSf' -import { ValueSc } from '../components/ValueSc' -import { ValueCopyable } from '../components/ValueCopyable' -import BigNumber from 'bignumber.js' -import { upperFirst } from 'lodash' -import { EntityType, getEntityTypeLabel } from '../lib/entityTypes' - -// entityType&entityValue | value | values | sc | sf -export type DatumProps = { - label: string - value?: React.ReactNode - hash?: string - href?: string - entityType?: EntityType - entityValue?: string - sc?: BigNumber - sf?: number - comment?: React.ReactNode -} - -export function Datum({ - label, - entityType, - entityValue, - href, - value, - hash, - sc, - sf, - comment, -}: DatumProps) { - return ( -
-
- - {upperFirst(label)} - -
-
- {sc !== undefined && ( - - )} - {sf !== undefined && ( - - )} - {entityType && - (entityValue ? ( - - ) : ( - - - - - ))} - {hash && } - {value !== undefined && ( - - {value} - - )} - {comment && ( - - {comment} - - )} -
-
- ) -} diff --git a/libs/design-system/src/components/EntityAvatar.tsx b/libs/design-system/src/components/EntityAvatar.tsx index cd71cc1e1..702959d3b 100644 --- a/libs/design-system/src/components/EntityAvatar.tsx +++ b/libs/design-system/src/components/EntityAvatar.tsx @@ -1,32 +1,43 @@ +'use client' + import { Avatar } from '../core/Avatar' import { Link } from '../core/Link' import { Tooltip } from '../core/Tooltip' -import { - EntityType, - getEntityTypeInitials, - getEntityTypeLabel, -} from '../lib/entityTypes' +import { EntityType, getEntityTypeLabel } from '../lib/entityTypes' type Props = { initials?: string type?: EntityType label?: string href?: string + src?: string shape?: 'square' | 'circle' } -export function EntityAvatar({ type, label, initials, href, shape }: Props) { +export function EntityAvatar({ + type, + label, + initials, + href, + src, + shape, +}: Props) { const avatarEl = ( ) - const linkEl = href && {avatarEl} + const linkEl = href && ( + + {avatarEl} + + ) const el = linkEl || avatarEl if (type) { @@ -39,3 +50,10 @@ export function EntityAvatar({ type, label, initials, href, shape }: Props) { return el } + +function initializeWords(str: string) { + return str + .split(' ') + .map((word) => word.charAt(0).toUpperCase) + .join('') +} diff --git a/libs/design-system/src/components/EntityList.tsx b/libs/design-system/src/components/EntityList.tsx index c899e83aa..5692f52f6 100644 --- a/libs/design-system/src/components/EntityList.tsx +++ b/libs/design-system/src/components/EntityList.tsx @@ -1,19 +1,18 @@ +'use client' + import { Heading } from '../core/Heading' import { Link } from '../core/Link' import { Panel } from '../core/Panel' import { Text } from '../core/Text' -import { Skeleton } from '../core/Skeleton' import { ValueSf } from '../components/ValueSf' import { ValueSc } from '../components/ValueSc' import { ValueCopyable } from '../components/ValueCopyable' import { EntityType, - getEntityTypeInitials, getEntityTypeLabel, getTxTypeLabel, TxType, } from '../lib/entityTypes' -import { times } from 'lodash' import { humanNumber } from '@siafoundation/sia-js' import { formatDistance } from 'date-fns' import { upperFirst } from 'lodash' @@ -22,6 +21,7 @@ import React from 'react' import BigNumber from 'bignumber.js' import { cx } from 'class-variance-authority' import { DotMark16 } from '@carbon/icons-react' +import { EntityListSkeleton } from './EntityListSkeleton' export type EntityListItemProps = { label?: string @@ -34,10 +34,13 @@ export type EntityListItemProps = { initials?: string sc?: BigNumber sf?: number + scVariant?: 'value' | 'change' + sfVariant?: 'value' | 'change' height?: number timestamp?: number unconfirmed?: boolean avatarShape?: 'square' | 'circle' + avatar?: string } type Props = { @@ -45,9 +48,16 @@ type Props = { actions?: React.ReactNode entities?: EntityListItemProps[] emptyMessage?: string + skeletonCount?: number } -export function EntityList({ title, actions, entities, emptyMessage }: Props) { +export function EntityList({ + title, + actions, + entities, + emptyMessage, + skeletonCount = 10, +}: Props) { const showHeading = title || actions return ( @@ -67,7 +77,7 @@ export function EntityList({ title, actions, entities, emptyMessage }: Props) { {entities?.length === 0 && (
@@ -95,7 +105,7 @@ export function EntityList({ title, actions, entities, emptyMessage }: Props) { getTxTypeLabel(entity.txType)) || getEntityTypeLabel(entity.type) - const title = upperFirst(label) + const title = isValidUrl(label) ? label : upperFirst(label) return (
@@ -124,8 +136,8 @@ export function EntityList({ title, actions, entities, emptyMessage }: Props) { {title || truncHashEl}
- {!!sc && } - {!!sf && } + {!!sc && } + {!!sf && }
{!!title && truncHashEl}
@@ -156,35 +168,35 @@ export function EntityList({ title, actions, entities, emptyMessage }: Props) {
) - }) || } + }) || }
) } -export function EntityListSkeleton() { - return ( - <> - {times(10, (i) => ( -
- -
- - -
-
- ))} - - ) -} - -export function itemBorderStyles() { +function itemBorderStyles() { return cx( 'border-t border-gray-200 dark:border-graydark-300', 'first:border-none' ) } + +function initializeWords(str: string) { + return str + .split(' ') + .map((word) => word.charAt(0).toUpperCase()) + .join('') +} + +function isValidUrl(url?: string) { + if (!url) { + return false + } + try { + new URL(url) + return true + } catch { + return false + } +} diff --git a/libs/design-system/src/components/EntityListSkeleton.tsx b/libs/design-system/src/components/EntityListSkeleton.tsx new file mode 100644 index 000000000..9eeda1c99 --- /dev/null +++ b/libs/design-system/src/components/EntityListSkeleton.tsx @@ -0,0 +1,29 @@ +import { Skeleton } from '../core/Skeleton' +import { times } from 'lodash' +import { cx } from 'class-variance-authority' + +export function EntityListSkeleton({ skeletonCount = 10 }) { + return ( + <> + {times(skeletonCount, (i) => ( +
+ +
+ + +
+
+ ))} + + ) +} + +function itemBorderStyles() { + return cx( + 'border-t border-gray-200 dark:border-graydark-300', + 'first:border-none' + ) +} diff --git a/libs/design-system/src/components/PaginatorKnownTotal.tsx b/libs/design-system/src/components/PaginatorKnownTotal.tsx index 6eeb4176f..3e74952b9 100644 --- a/libs/design-system/src/components/PaginatorKnownTotal.tsx +++ b/libs/design-system/src/components/PaginatorKnownTotal.tsx @@ -1,3 +1,5 @@ +'use client' + import { Button } from '../core/Button' import { ControlGroup } from '../core/ControlGroup' import { diff --git a/libs/design-system/src/components/PaginatorUnknownTotal.tsx b/libs/design-system/src/components/PaginatorUnknownTotal.tsx index b586b4238..b04c6bf69 100644 --- a/libs/design-system/src/components/PaginatorUnknownTotal.tsx +++ b/libs/design-system/src/components/PaginatorUnknownTotal.tsx @@ -1,3 +1,5 @@ +'use client' + import { Button } from '../core/Button' import { ControlGroup } from '../core/ControlGroup' import { CaretLeft16, CaretRight16, PageFirst16 } from '../icons/carbon' diff --git a/libs/design-system/src/components/ThemeRadio.tsx b/libs/design-system/src/components/ThemeRadio.tsx index f13174261..338c251f5 100644 --- a/libs/design-system/src/components/ThemeRadio.tsx +++ b/libs/design-system/src/components/ThemeRadio.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import { useTheme } from 'next-themes' import { Asleep16, Awake16, Screen16 } from '../icons/carbon' diff --git a/libs/design-system/src/components/UserDropdownMenu.tsx b/libs/design-system/src/components/UserDropdownMenu.tsx index bb3fcda7f..0b8726a37 100644 --- a/libs/design-system/src/components/UserDropdownMenu.tsx +++ b/libs/design-system/src/components/UserDropdownMenu.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import { DropdownMenu, diff --git a/libs/design-system/src/components/ValueCopyable.tsx b/libs/design-system/src/components/ValueCopyable.tsx index e5b0aa990..0d86ededc 100644 --- a/libs/design-system/src/components/ValueCopyable.tsx +++ b/libs/design-system/src/components/ValueCopyable.tsx @@ -1,3 +1,5 @@ +'use client' + import { Text } from '../core/Text' import { Button } from '../core/Button' import { Link } from '../core/Link' @@ -14,6 +16,7 @@ type Props = { label?: string href?: string size?: React.ComponentProps['size'] + weight?: React.ComponentProps['weight'] scaleSize?: React.ComponentProps['scaleSize'] maxLength?: number color?: React.ComponentProps['color'] @@ -29,6 +32,7 @@ export function ValueCopyable({ maxLength: customMaxLength, size, scaleSize, + weight, color = 'contrast', className, }: Props) { @@ -50,12 +54,19 @@ export function ValueCopyable({ size={size} scaleSize={scaleSize} color={color} + weight={weight} ellipsis > {text} ) : ( - + {text} )} diff --git a/libs/design-system/src/components/ValueSc.tsx b/libs/design-system/src/components/ValueSc.tsx index 6bde57457..5813d0eb4 100644 --- a/libs/design-system/src/components/ValueSc.tsx +++ b/libs/design-system/src/components/ValueSc.tsx @@ -1,3 +1,5 @@ +'use client' + import { Text } from '../core/Text' import { Tooltip } from '../core/Tooltip' import { humanSiacoin } from '@siafoundation/sia-js' diff --git a/libs/design-system/src/components/ValueSf.tsx b/libs/design-system/src/components/ValueSf.tsx index 8b55a1afc..a3b0d7e84 100644 --- a/libs/design-system/src/components/ValueSf.tsx +++ b/libs/design-system/src/components/ValueSf.tsx @@ -1,3 +1,5 @@ +'use client' + import { Text } from '../core/Text' import { Tooltip } from '../core/Tooltip' import { humanNumber } from '@siafoundation/sia-js' diff --git a/libs/design-system/src/config/colors.ts b/libs/design-system/src/config/colors.ts new file mode 100644 index 000000000..3514965c7 --- /dev/null +++ b/libs/design-system/src/config/colors.ts @@ -0,0 +1,117 @@ +import { + inherit, + current, + transparent, + black, + white, + slate, + // gray, + zinc, + neutral, + stone, + red, + orange, + amber, + yellow, + lime, + green, + emerald, + teal, + cyan, + sky, + blue, + indigo, + violet, + purple, + fuchsia, + pink, + rose, +} from 'tailwindcss/colors' + +// manually sync with theme-colors.js +export const colors = { + inherit, + current, + transparent, + black, + white, + slate, + // gray, + zinc, + neutral, + stone, + red, + orange, + amber, + yellow, + lime, + green, + emerald, + teal, + cyan, + sky, + blue, + indigo, + violet, + purple, + fuchsia, + pink, + rose, + mask: 'rgba(30, 169, 76, .3)', + accent: { + 50: '#F6FFF9', + 100: '#E9FBEF', + 200: '#D5F2DE', + 300: '#C9EED4', + 400: '#BBE8C9', + 500: '#9BDAAE', + 600: '#71C48A', + 700: '#3EAA5F', + 800: '#05872D', + 900: '#056B24', + 1000: '#04511B', + 1100: '#011F0A', + }, + accentdark: { + 50: '#05150A', + 100: '#051C0C', + 200: '#062811', + 300: '#073515', + 400: '#064018', + 500: '#075820', + 600: '#056824', + 700: '#077A2A', + 800: '#05872D', + 900: '#069F35', + 1000: '#2FA052', + 1100: '#D9F4E1', + }, + gray: { + 50: 'hsl(206, 30.0%, 98.8%)', + 100: 'hsl(210, 16.7%, 97.6%)', + 200: 'hsl(209, 13.3%, 95.3%)', + 300: 'hsl(209, 12.2%, 93.2%)', + 400: 'hsl(208, 11.7%, 91.1%)', + 500: 'hsl(208, 11.3%, 88.9%)', + 600: 'hsl(207, 11.1%, 85.9%)', + 700: 'hsl(205, 10.7%, 78.0%)', + 800: 'hsl(206, 6.0%, 56.1%)', + 900: 'hsl(206, 5.8%, 52.3%)', + 1000: 'hsl(206, 6.0%, 43.5%)', + 1100: 'hsl(206, 24.0%, 9.0%)', + }, + graydark: { + 50: 'hsl(200, 7.0%, 8.8%)', + 100: 'hsl(195, 7.1%, 11.0%)', + 200: 'hsl(197, 6.8%, 13.6%)', + 300: 'hsl(198, 6.6%, 15.8%)', + 400: 'hsl(199, 6.4%, 17.9%)', + 500: 'hsl(201, 6.2%, 20.5%)', + 600: 'hsl(203, 6.0%, 24.3%)', + 700: 'hsl(207, 5.6%, 31.6%)', + 800: 'hsl(206, 6.0%, 43.9%)', + 900: 'hsl(206, 5.2%, 49.5%)', + 1000: 'hsl(206, 6.0%, 63.0%)', + 1100: 'hsl(210, 6.0%, 93.0%)', + }, +} diff --git a/libs/design-system/src/config/colors.js b/libs/design-system/src/config/theme-colors.js similarity index 98% rename from libs/design-system/src/config/colors.js rename to libs/design-system/src/config/theme-colors.js index 740a44603..f745a5799 100644 --- a/libs/design-system/src/config/colors.js +++ b/libs/design-system/src/config/theme-colors.js @@ -28,6 +28,7 @@ const { rose, } = require('tailwindcss/colors') +// manually sync with colors.ts module.exports = { colors: { inherit, diff --git a/libs/design-system/src/config/theme.js b/libs/design-system/src/config/theme.js index e24b5e42e..8e27b4cd4 100644 --- a/libs/design-system/src/config/theme.js +++ b/libs/design-system/src/config/theme.js @@ -1,6 +1,6 @@ const plugin = require('tailwindcss/plugin') const { fontFamily } = require('tailwindcss/defaultTheme') -const { colors } = require('./colors') +const { colors } = require('./theme-colors') module.exports = { darkMode: 'class', diff --git a/libs/design-system/src/core/Accordion.tsx b/libs/design-system/src/core/Accordion.tsx index ea862c871..3ef7acc0b 100644 --- a/libs/design-system/src/core/Accordion.tsx +++ b/libs/design-system/src/core/Accordion.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import * as AccordionPrimitive from '@radix-ui/react-accordion' import { ChevronDown16 } from '../icons/carbon' diff --git a/libs/design-system/src/core/Avatar.tsx b/libs/design-system/src/core/Avatar.tsx index 2e09744a0..e6c80d7cc 100644 --- a/libs/design-system/src/core/Avatar.tsx +++ b/libs/design-system/src/core/Avatar.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import * as AvatarPrimitive from '@radix-ui/react-avatar' import { cva, cx } from 'class-variance-authority' @@ -8,6 +10,7 @@ const avatarStyles = cva( 'items-center justify-center align-middle flex flex-shrink-0 relative', 'overflow-hidden select-none outline-none', 'font-sans font-medium text-sm', + 'border', ], { variants: { @@ -15,11 +18,18 @@ const avatarStyles = cva( '1': 'w-6 h-6', '2': 'w-12 h-12', '3': 'w-16 h-16', + '4': 'w-28 h-28', }, variant: { filter: 'bg-transparent', - hiContrast: 'bg-gray-900 dark:bg-white text-white dark:text-gray-1100', - gray: 'bg-gray-500 dark:bg-gray-800 text-gray-1100 dark:text-white', + hiContrast: [ + 'bg-gray-900 dark:bg-white text-white dark:text-gray-1100', + 'border-gray-900 dark:border-white text-white dark:text-gray-1100', + ], + gray: [ + 'bg-gray-500 dark:bg-gray-800 text-gray-1100 dark:text-white', + 'border-gray-500 dark:border-gray-800 text-gray-1100 dark:text-white', + ], }, shape: { square: 'rounded', @@ -46,6 +56,7 @@ const fallbackStyles = cva('uppercase', { '1': 'text-sm', '2': 'text-base', '3': 'text-lg', + '4': 'text-lg', }, }, defaultVariants: { diff --git a/libs/design-system/src/core/Button.tsx b/libs/design-system/src/core/Button.tsx index 8c94309e5..fa5878ab9 100644 --- a/libs/design-system/src/core/Button.tsx +++ b/libs/design-system/src/core/Button.tsx @@ -1,3 +1,5 @@ +'use client' + import { cva, VariantProps } from 'class-variance-authority' import React from 'react' import { Tooltip } from './Tooltip' diff --git a/libs/design-system/src/core/Checkbox.tsx b/libs/design-system/src/core/Checkbox.tsx index f4a287ed4..d0d671c55 100644 --- a/libs/design-system/src/core/Checkbox.tsx +++ b/libs/design-system/src/core/Checkbox.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import * as CheckboxPrimitive from '@radix-ui/react-checkbox' import { Checkmark16 } from '../icons/carbon' diff --git a/libs/design-system/src/core/ComboBox.tsx b/libs/design-system/src/core/ComboBox.tsx deleted file mode 100644 index 9981f26ad..000000000 --- a/libs/design-system/src/core/ComboBox.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import { useMemo } from 'react' -import { - components, - GroupBase, - SelectComponentsConfig, - SingleValue, -} from 'react-select' -import Select from 'react-select/async' -import { cx } from 'class-variance-authority' - -type Option = { - value: string - label: string -} - -type Props = { - value?: Option - onChange?: (option: SingleValue