diff --git a/.vscode/astral.code-workspace b/.vscode/astral.code-workspace index ede5a4de3..25252ec54 100644 --- a/.vscode/astral.code-workspace +++ b/.vscode/astral.code-workspace @@ -48,6 +48,7 @@ "editor.formatOnSave": true, "cSpell.words": [ "autonomys", + "autoid", "extrinsics", "gemini", "graphiql", diff --git a/.vscode/settings.json b/.vscode/settings.json index b3a0839e6..7486204a8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,7 @@ "editor.formatOnSave": true, "cSpell.words": [ "autonomys", + "autoid", "extrinsics", "gemini", "graphiql", diff --git a/README.md b/README.md index 1ba3e0d0a..1534927aa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![build](https://github.com/autonomys//blockexplorer/actions/workflows/build.yaml/badge.svg) +![build](https://github.com/autonomys/astral/actions/workflows/build.yaml/badge.svg) # Astral diff --git a/explorer/gql/oldSquidTypes.ts b/explorer/gql/oldSquidTypes.ts index 2d4939d8f..c647a055e 100644 --- a/explorer/gql/oldSquidTypes.ts +++ b/explorer/gql/oldSquidTypes.ts @@ -2602,6 +2602,7 @@ export type AllRewardForAccountByIdQuery = { __typename?: 'Query', accountReward export type BlocksConnectionQueryVariables = Exact<{ first: Scalars['Int']['input']; after?: InputMaybe; + orderBy: Array | BlockOrderByInput; }>; diff --git a/explorer/next.config.js b/explorer/next.config.js index 0435f2a8b..21d355cc3 100644 --- a/explorer/next.config.js +++ b/explorer/next.config.js @@ -1,6 +1,17 @@ /** @type {import('next').NextConfig;} */ const nextConfig = { reactStrictMode: true, + images: { + formats: ['image/avif', 'image/webp'], + remotePatterns: [ + { + protocol: 'https', + hostname: 'docs.subspace.network', + port: '', + pathname: '**', + }, + ], + }, webpack: (config, { isServer }) => { config.module.rules.push({ test: /\.svg$/, diff --git a/explorer/public/images/share-farming.png b/explorer/public/images/share-farming.png new file mode 100644 index 000000000..de9a1ce24 Binary files /dev/null and b/explorer/public/images/share-farming.png differ diff --git a/explorer/src/app/[chain]/autoid/layout.tsx b/explorer/src/app/[chain]/autoid/layout.tsx new file mode 100644 index 000000000..93efb8e5a --- /dev/null +++ b/explorer/src/app/[chain]/autoid/layout.tsx @@ -0,0 +1,7 @@ +import { DomainHeader } from 'components/layout/DomainHeader' +import { MainLayout } from 'components/layout/Layout' +import type { ChildrenPageProps } from 'types/app' + +export default async function Layout({ children }: ChildrenPageProps) { + return }>{children} +} diff --git a/explorer/src/app/[chain]/autoid/page.tsx b/explorer/src/app/[chain]/autoid/page.tsx new file mode 100644 index 000000000..99a289d7d --- /dev/null +++ b/explorer/src/app/[chain]/autoid/page.tsx @@ -0,0 +1,29 @@ +import { AutoIdPage } from '@/components/AutoId' +import { chains } from 'constants/chains' +import { metadata } from 'constants/metadata' +import { Metadata } from 'next' +import { FC } from 'react' +import type { ChainPageProps } from 'types/app' + +export async function generateMetadata({ params: { chain } }: ChainPageProps): Promise { + const chainTitle = chains.find((c) => c.urls.page === chain)?.title || 'Unknown chain' + const title = `${metadata.title} - ${chainTitle} - Auto ID` + return { + ...metadata, + title, + openGraph: { + ...metadata.openGraph, + title, + }, + twitter: { + ...metadata.twitter, + title, + }, + } +} + +const Page: FC = () => { + return +} + +export default Page diff --git a/explorer/src/app/[chain]/domains/layout.tsx b/explorer/src/app/[chain]/domains/layout.tsx new file mode 100644 index 000000000..93efb8e5a --- /dev/null +++ b/explorer/src/app/[chain]/domains/layout.tsx @@ -0,0 +1,7 @@ +import { DomainHeader } from 'components/layout/DomainHeader' +import { MainLayout } from 'components/layout/Layout' +import type { ChildrenPageProps } from 'types/app' + +export default async function Layout({ children }: ChildrenPageProps) { + return }>{children} +} diff --git a/explorer/src/app/[chain]/domains/page.tsx b/explorer/src/app/[chain]/domains/page.tsx new file mode 100644 index 000000000..7fb4c9ede --- /dev/null +++ b/explorer/src/app/[chain]/domains/page.tsx @@ -0,0 +1,29 @@ +import { DomainPage } from 'components/Domain' +import { chains } from 'constants/chains' +import { metadata } from 'constants/metadata' +import { Metadata } from 'next' +import { FC } from 'react' +import type { ChainPageProps } from 'types/app' + +export async function generateMetadata({ params: { chain } }: ChainPageProps): Promise { + const chainTitle = chains.find((c) => c.urls.page === chain)?.title || 'Unknown chain' + const title = `${metadata.title} - ${chainTitle} - Domain` + return { + ...metadata, + title, + openGraph: { + ...metadata.openGraph, + title, + }, + twitter: { + ...metadata.twitter, + title, + }, + } +} + +const Page: FC = () => { + return +} + +export default Page diff --git a/explorer/src/app/[chain]/farming/layout.tsx b/explorer/src/app/[chain]/farming/layout.tsx new file mode 100644 index 000000000..445e6f865 --- /dev/null +++ b/explorer/src/app/[chain]/farming/layout.tsx @@ -0,0 +1,7 @@ +import { FarmingHeader } from 'components/layout/FarmingHeader' +import { MainLayout } from 'components/layout/Layout' +import type { ChildrenPageProps } from 'types/app' + +export default async function Layout({ children }: ChildrenPageProps) { + return }>{children} +} diff --git a/explorer/src/app/[chain]/farming/page.tsx b/explorer/src/app/[chain]/farming/page.tsx new file mode 100644 index 000000000..3ffe74b12 --- /dev/null +++ b/explorer/src/app/[chain]/farming/page.tsx @@ -0,0 +1,38 @@ +import { DownloadPage } from '@/components/Farming' +import { chains } from 'constants/chains' +import { metadata, url } from 'constants/metadata' +import { Metadata } from 'next' +import { FC } from 'react' +import type { ChainPageProps } from 'types/app' + +export async function generateMetadata({ params: { chain } }: ChainPageProps): Promise { + const chainTitle = chains.find((c) => c.urls.page === chain)?.title || 'Unknown chain' + const title = `${metadata.title} - ${chainTitle} - Farming` + const images = { + url: url + '/images/share-farming.png', + secureUrl: url + 'image/png', + width: 900, + height: 600, + alt: title, + } + return { + ...metadata, + title, + openGraph: { + ...metadata.openGraph, + title, + images, + }, + twitter: { + ...metadata.twitter, + title, + images, + }, + } +} + +const Page: FC = () => { + return +} + +export default Page diff --git a/explorer/src/components/Account/AccountRewardList.tsx b/explorer/src/components/Account/AccountRewardList.tsx index 6c844b241..ee53c37da 100644 --- a/explorer/src/components/Account/AccountRewardList.tsx +++ b/explorer/src/components/Account/AccountRewardList.tsx @@ -8,7 +8,7 @@ import { SortingState } from '@tanstack/react-table' import { SortedTable } from 'components/common/SortedTable' import { Spinner } from 'components/common/Spinner' import { PAGE_SIZE } from 'constants/general' -import { INTERNAL_ROUTES } from 'constants/routes' +import { INTERNAL_ROUTES, Routes } from 'constants/routes' import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { RewardEventOrderByInput, RewardsListQueryVariables } from 'gql/graphql' @@ -73,6 +73,8 @@ export const AccountRewardList: FC = () => { skip: !inFocus, pollInterval: 6000, }, + selectedChain?.isDomain ? Routes.nova : Routes.consensus, + 'accountReward', ) const { @@ -208,9 +210,12 @@ export const AccountRewardList: FC = () => { useEffect(() => { setIsVisible(inView) }, [inView, setIsVisible]) + return (
- + {convertedAddress && ( + + )}
{`Rewards (${totalLabel})`}
diff --git a/explorer/src/components/AutoId/index.tsx b/explorer/src/components/AutoId/index.tsx new file mode 100644 index 000000000..48b9f2bb4 --- /dev/null +++ b/explorer/src/components/AutoId/index.tsx @@ -0,0 +1,27 @@ +'use client' + +import { FC } from 'react' + +export const AutoIdPage: FC = () => { + return ( +
+
+
+
+

+ Auto-ID +

+
+
+
+

+ Auto ID is our first primitive enabling identity infrastructure at massive scale. + Documentation coming soon. +

+
+
+
+
+
+ ) +} diff --git a/explorer/src/components/Block/BlockList.tsx b/explorer/src/components/Block/BlockList.tsx index fc1e09d64..fec3f891a 100644 --- a/explorer/src/components/Block/BlockList.tsx +++ b/explorer/src/components/Block/BlockList.tsx @@ -12,6 +12,7 @@ import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { Block, + BlockOrderByInput, BlocksConnectionDomainQuery, BlocksConnectionDomainQueryVariables, BlocksConnectionQuery, @@ -49,6 +50,7 @@ export const BlockList: FC = () => { [selectedChain.isDomain], ) + const orderBy = useMemo(() => BlockOrderByInput.HeightDesc, []) const variables = useMemo( () => ({ first: pagination.pageSize, @@ -56,8 +58,9 @@ export const BlockList: FC = () => { pagination.pageIndex > 0 ? (pagination.pageIndex * pagination.pageSize).toString() : undefined, + orderBy, }), - [pagination.pageSize, pagination.pageIndex], + [pagination.pageSize, pagination.pageIndex, orderBy], ) const { setIsVisible } = useSquidQuery< diff --git a/explorer/src/components/Block/query.ts b/explorer/src/components/Block/query.ts index 5cf21ad1e..041e6561a 100644 --- a/explorer/src/components/Block/query.ts +++ b/explorer/src/components/Block/query.ts @@ -1,8 +1,8 @@ import { gql } from '@apollo/client' export const QUERY_BLOCK_LIST_CONNECTION = gql` - query BlocksConnection($first: Int!, $after: String) { - blocksConnection(orderBy: height_DESC, first: $first, after: $after) { + query BlocksConnection($first: Int!, $after: String, $orderBy: [BlockOrderByInput!]!) { + blocksConnection(orderBy: $orderBy, first: $first, after: $after) { edges { cursor node { diff --git a/explorer/src/components/Domain/index.tsx b/explorer/src/components/Domain/index.tsx new file mode 100644 index 000000000..c12174dff --- /dev/null +++ b/explorer/src/components/Domain/index.tsx @@ -0,0 +1,95 @@ +'use client' + +import { BlockIcon, DocIcon } from '@/components/icons' +import { Routes } from 'constants/routes' +import useDomains from 'hooks/useDomains' +import Link from 'next/link' +import { FC, useMemo } from 'react' + +export const DomainPage: FC = () => { + const { selectedChain } = useDomains() + + const listOfCards = useMemo( + () => [ + { + title: 'Nova', + description: 'EVM domain', + href: `/${selectedChain.urls.page}/${Routes.nova}`, + icon: , + darkBgClass: + 'dark:bg-gradient-to-b dark:from-purpleLighterAccent dark:via-purpleMedium dark:to-purplePale', + }, + { + title: 'Auto-ID', + description: 'Identity domain', + href: `/${selectedChain.urls.page}/${Routes.autoid}`, + icon: , + darkBgClass: 'dark:bg-gradient-to-b dark:from-purpleDeep dark:to-purplePastel', + }, + ], + [selectedChain.urls.page], + ) + + return ( +
+
+
+
+

+ Domains +

+
+
+
+

+ The Autonomys Network can run multiple domains, each with different runtimes, + genesis configurations, and validator sets. Each domain is a separate blockchain + with its own state and history. Operators for each domain are responsible for + preparing blocks of transactions. Committing these domain blocks as bundles to the + Consensus Layer is done on a per-domain basis. +

+
+
+

+ At the moment, we have an EVM (Ethereum Virtual Machine) domain and a custom + decentralized identity domain. However, more custom or existing runtime domains + could be deployed in the future. +

+
+
+
+ +
+
+
+ {listOfCards.map(({ title, description, href, icon, darkBgClass }, index) => ( + +
+
+
+ {icon} +
+
+

+ {title} +

+

+ {description} +

+
+
+
+ + ))} +
+
+
+
+
+ ) +} diff --git a/explorer/src/components/Farming/index.tsx b/explorer/src/components/Farming/index.tsx new file mode 100644 index 000000000..013220a19 --- /dev/null +++ b/explorer/src/components/Farming/index.tsx @@ -0,0 +1,238 @@ +'use client' + +import { EXTERNAL_ROUTES } from '@/constants' +import Image from 'next/image' +import { FC, useCallback, useEffect, useMemo, useState } from 'react' + +interface ReleaseAsset { + name: string + browser_download_url: string +} + +interface ReleaseData { + assets: ReleaseAsset[] +} + +export const DownloadPage: FC = () => { + const [userOS, setUserOS] = useState(null) + const [releaseAssets, setReleaseAssets] = useState([]) + + useEffect(() => { + const getOS = () => { + const userAgent = window.navigator.userAgent + if (userAgent.indexOf('Win') !== -1) return 'Windows' + if (userAgent.indexOf('Mac') !== -1) return 'macOS' + if (userAgent.indexOf('Linux') !== -1) return 'Linux' + return null + } + + setUserOS(getOS()) + }, []) + + useEffect(() => { + const fetchLatestRelease = async () => { + try { + const response = await fetch(EXTERNAL_ROUTES.spaceAcres) + if (response.status === 200) { + const data: ReleaseData = await response.json() + setReleaseAssets(data.assets) + } + } catch (error) { + console.error('Error fetching latest release:', error) + } + } + + fetchLatestRelease() + }, []) + + const getDownloadLink = useCallback( + (os: string) => { + switch (os) { + case 'Windows': + return releaseAssets.find((asset) => asset.name.endsWith('.msi'))?.browser_download_url + case 'macOS': + return releaseAssets.find((asset) => asset.name.endsWith('.dmg'))?.browser_download_url + case 'Linux': + return releaseAssets.find((asset) => asset.name.endsWith('.deb'))?.browser_download_url + default: + return '#' + } + }, + [releaseAssets], + ) + + const getAssetName = useCallback( + (os: string) => { + switch (os) { + case 'Windows': + return releaseAssets.find((asset) => asset.name.endsWith('.msi'))?.name + case 'macOS': + return releaseAssets.find((asset) => asset.name.endsWith('.dmg'))?.name + case 'Linux': + return releaseAssets.find((asset) => asset.name.endsWith('.deb'))?.name + default: + return '' + } + }, + [releaseAssets], + ) + + const renderDownloadSection = useMemo(() => { + const downloadLink = getDownloadLink(userOS || '') + + switch (userOS) { + case 'Windows': + return ( +
+

Download Space Acres for Windows

+

Minimum Requirements:

+
    +
  • OS: Windows 10 or higher
  • +
  • CPU: 4 Core
  • +
  • RAM: 8 GB
  • +
  • Disk Space: 100 GB
  • +
+ + Download +
Windows
+
+

Installation Instructions:

+
    +
  1. Download the installer from the link above.
  2. +
  3. Run the installer and follow the on-screen instructions.
  4. +
  5. Once installed, open Space Acres from your Start menu.
  6. +
+
+ ) + case 'macOS': + return ( +
+

Download Space Acres for macOS

+

Minimum Requirements:

+
    +
  • OS: macOS 10.14 or higher
  • +
  • Processor: Intel Core i3 or equivalent
  • +
  • RAM: 4 GB
  • +
  • Disk Space: 500 MB
  • +
+ + Download +
macOS
+
+

Installation Instructions:

+
    +
  1. Download the DMG file from the link above.
  2. +
  3. Open the DMG file and drag Space Acres to your Applications folder.
  4. +
  5. Launch Space Acres from the Applications folder.
  6. +
+
+ ) + case 'Linux': + return ( +
+

Download Space Acres for Linux

+

Minimum Requirements:

+
    +
  • OS: Ubuntu 18.04 or higher
  • +
  • Processor: Intel Core i3 or equivalent
  • +
  • RAM: 4 GB
  • +
  • Disk Space: 500 MB
  • +
+ + Download +
Linux
+
+

Installation Instructions:

+
    +
  1. Download the tar.gz file from the link above.
  2. +
  3. Extract the file to your desired location.
  4. +
  5. Open a terminal and navigate to the extracted folder.
  6. +
  7. Run `./install.sh` to install Space Acres.
  8. +
+
+ ) + default: + return

Sorry, your operating system is not supported.

+ } + }, [getDownloadLink, userOS]) + + const downloadButton = useMemo( + () => ( + + ), + [getDownloadLink, getAssetName, userOS], + ) + + return ( +
+
+
+
+

+ Put your unused disk space to work and contribute to the Network +

+ {renderDownloadSection && downloadButton} +
+
+ Space Acres Screenshot Installation +
+
+ +
+

+ By contributing storage and compute to the network, you play a crucial role in securing + it, while also earning rewards. +

+ + +
+ +
+
+
{renderDownloadSection}
+
{renderDownloadSection && downloadButton}
+
+

Support and Documentation

+

+ For more detailed instructions, troubleshooting, and advanced usage, please refer to + the{' '} + + Space Acres documentation + + . +

+

+ Need help? Visit our{' '} + + Discord server + + . +

+
+
+
+
+
+ ) +} diff --git a/explorer/src/components/WalletButton/AccountListDropdown.tsx b/explorer/src/components/WalletButton/AccountListDropdown.tsx index 6e335ec75..2fa45d81d 100644 --- a/explorer/src/components/WalletButton/AccountListDropdown.tsx +++ b/explorer/src/components/WalletButton/AccountListDropdown.tsx @@ -2,9 +2,9 @@ import { PolkadotIcon } from '@/components/icons/PolkadotIcon' import { SubWalletIcon } from '@/components/icons/SubWalletIcon' -import { shortString } from '@/utils/string' +import { limitText, shortString } from '@/utils/string' import { Listbox, Transition } from '@headlessui/react' -import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid' +import { ChevronDownIcon } from '@heroicons/react/20/solid' import { SupportedWalletExtension, WalletType } from 'constants/wallet' import useMediaQuery from 'hooks/useMediaQuery' import useWallet from 'hooks/useWallet' @@ -40,31 +40,27 @@ function AccountListDropdown() { - `relative cursor-default select-none py-2 pr-4 text-gray-900 dark:text-white md:pl-10 ${ - active && 'dark:bg-blueDarkAccent bg-gray-100' + `w-120 relative cursor-pointer select-none py-2 text-gray-900 dark:text-white ${ + active && 'bg-gray-100 dark:bg-blueDarkAccent' }` } value={account} > {({ selected }) => { const subAccount = - account.type === WalletType.subspace + account.type === WalletType.subspace || + (account as { type: string }).type === 'sr25519' ? formatAddress(account.address) : account.address const formattedAccount = subAccount && shortString(subAccount) return (
- {account.name} + {account.name ? limitText(account.name, 16) : 'Account ' + chainIdx} {formattedAccount} - {selected ? ( - - - ) : null}
) }} @@ -88,8 +84,8 @@ function AccountListDropdown() {
@@ -117,11 +113,11 @@ function AccountListDropdown() { leaveFrom='opacity-100' leaveTo='opacity-0' > - + {walletList} diff --git a/explorer/src/components/common/OutOfSyncBanner.tsx b/explorer/src/components/common/OutOfSyncBanner.tsx index 689fe8617..1b6281116 100644 --- a/explorer/src/components/common/OutOfSyncBanner.tsx +++ b/explorer/src/components/common/OutOfSyncBanner.tsx @@ -62,13 +62,14 @@ export const useOutOfSyncBanner = () => { const outOfSyncBanner = useMemo( () => + !selectedChain.isDomain && data && lastBlock && lastChainBlock !== null && lastBlock + NORMAL_BLOCKS_DIVERGENCE < lastChainBlock ? ( ) : null, - [data, lastBlock, lastChainBlock], + [data, lastBlock, lastChainBlock, selectedChain.isDomain], ) useEffect(() => { diff --git a/explorer/src/components/layout/DomainHeader.tsx b/explorer/src/components/layout/DomainHeader.tsx index 72aa054db..e8ef9d81e 100644 --- a/explorer/src/components/layout/DomainHeader.tsx +++ b/explorer/src/components/layout/DomainHeader.tsx @@ -1,100 +1,117 @@ 'use client' -import { CpuChipIcon, GlobeAltIcon, QueueListIcon, TrophyIcon } from '@heroicons/react/24/outline' -import { WalletButton } from 'components/WalletButton' -import { WalletSidekick } from 'components/WalletSideKick' -import { chains } from 'constants/chains' -import { domains } from 'constants/domains' -import { ROUTES, Routes } from 'constants/routes' +import { Bars3BottomRightIcon, MoonIcon, SunIcon } from '@heroicons/react/24/outline' +import { LogoIcon } from 'components/icons' +import { INTERNAL_ROUTES, Routes } from 'constants/routes' import useDomains from 'hooks/useDomains' import useMediaQuery from 'hooks/useMediaQuery' -import useWallet from 'hooks/useWallet' import Link from 'next/link' -import { usePathname, useRouter } from 'next/navigation' -import { FC, useCallback, useMemo } from 'react' -import AccountListDropdown from '../WalletButton/AccountListDropdown' +import { usePathname } from 'next/navigation' +import { useTheme } from 'providers/ThemeProvider' +import { useMemo, useState } from 'react' +import { HeaderChainDropdown } from './HeaderChainDropdown' +import { MobileHeader } from './MobileHeader' -export const DomainHeader: FC = () => { - const isDesktop = useMediaQuery('(min-width: 1024px)') +export const DomainHeader = () => { + const { isDark, toggleTheme } = useTheme() const pathname = usePathname() + const isDesktop = useMediaQuery('(min-width: 1024px)') + const [isOpen, setIsOpen] = useState(false) + const { selectedChain } = useDomains() - const { push } = useRouter() - - const { setSelectedChain, selectedChain, setSelectedDomain } = useDomains() - const { actingAccount } = useWallet() - - const handleDomainSelected = useCallback( - (domain: string) => { - setSelectedDomain(domain) - if (domain === Routes.nova) setSelectedChain(domains[0]) - else setSelectedChain(chains[0]) - push(`/${selectedChain.urls.page}/${domain}`) - }, - [push, setSelectedChain, setSelectedDomain, selectedChain.urls.page], + const menuList = useMemo( + () => [ + { + title: 'Nova', + link: `/${selectedChain.urls.page}/${Routes.nova}`, + }, + { + title: 'Auto-ID', + link: `/${selectedChain.urls.page}/${Routes.autoid}`, + }, + ], + [selectedChain.urls.page], ) - const domainIcon = useCallback((domain: (typeof ROUTES)[0], isActive: boolean) => { - const className = `w-6 h-6 ${isActive ? 'text-white' : 'text-grayDark'} dark:text-white` - switch (domain.name) { - case Routes.nova: - return - case Routes.consensus: - return - case Routes.leaderboard: - return - case Routes.staking: - return - default: - return null - } - }, []) + return ( +
+ {isDesktop ? ( +
+ + + + + + +
+ + - + {isDark ? ( + + ) : ( + + )} +
- ) - }), - [handleDomainSelected, isDesktop, pathname, selectedChain.urls.page, domainIcon], - ) - - return ( -
-
-
{domainsOptions}
-
- {!actingAccount ? ( - - ) : ( -
- - -
- )}
-
-
+ ) : ( +
+ + + +
+ + +
+ +
+ )} +
) } diff --git a/explorer/src/components/layout/FarmingHeader.tsx b/explorer/src/components/layout/FarmingHeader.tsx new file mode 100644 index 000000000..169383ad2 --- /dev/null +++ b/explorer/src/components/layout/FarmingHeader.tsx @@ -0,0 +1,121 @@ +'use client' + +import { Bars3BottomRightIcon, MoonIcon, SunIcon } from '@heroicons/react/24/outline' +import { LogoIcon } from 'components/icons' +import { EXTERNAL_ROUTES, INTERNAL_ROUTES, Routes } from 'constants/routes' +import useDomains from 'hooks/useDomains' +import useMediaQuery from 'hooks/useMediaQuery' +import Link from 'next/link' +import { usePathname } from 'next/navigation' +import { useTheme } from 'providers/ThemeProvider' +import { useMemo, useState } from 'react' +import { HeaderChainDropdown } from './HeaderChainDropdown' +import { MobileHeader } from './MobileHeader' + +export const FarmingHeader = () => { + const { isDark, toggleTheme } = useTheme() + const pathname = usePathname() + const isDesktop = useMediaQuery('(min-width: 1024px)') + const [isOpen, setIsOpen] = useState(false) + const { selectedChain } = useDomains() + + const menuList = useMemo( + () => [ + { + title: 'Download Space Acres', + link: `/${selectedChain.urls.page}/${Routes.farming}`, + }, + { + title: 'Advance CLI', + link: `${EXTERNAL_ROUTES.docs}docs/category/advanced-cli`, + }, + { + title: 'Documentation', + link: `${EXTERNAL_ROUTES.docs}docs/farming-&-staking/farming/space-acres/space-acres-install`, + }, + ], + [selectedChain.urls.page], + ) + + return ( +
+ {isDesktop ? ( +
+ + + + + + +
+ + +
+
+ ) : ( +
+ + + +
+ + +
+ +
+ )} +
+ ) +} diff --git a/explorer/src/components/layout/Footer.tsx b/explorer/src/components/layout/Footer.tsx index beab1fccb..4a1717515 100644 --- a/explorer/src/components/layout/Footer.tsx +++ b/explorer/src/components/layout/Footer.tsx @@ -114,7 +114,7 @@ const Footer: FC = () => { className='text-xs text-whiteOpaque hover:text-purpleAccent' rel='noreferrer' > - Github + GitHub @@ -136,7 +136,7 @@ const Footer: FC = () => { className='text-xs text-whiteOpaque hover:text-purpleAccent' rel='noreferrer' > - Youtube + YouTube
  • diff --git a/explorer/src/components/layout/Layout.tsx b/explorer/src/components/layout/Layout.tsx index dabc60ea5..aa1f08f23 100644 --- a/explorer/src/components/layout/Layout.tsx +++ b/explorer/src/components/layout/Layout.tsx @@ -5,7 +5,7 @@ import { CookieBanner } from 'components/common/CookieBanner' import { ErrorFallback } from 'components/common/ErrorFallback' import { useOutOfSyncBanner } from 'components/common/OutOfSyncBanner' import { Container } from 'components/layout/Container' -import { DomainHeader } from 'components/layout/DomainHeader' +import { SectionHeader } from 'components/layout/SectionHeader' import { usePathname } from 'next/navigation' import { FC, ReactNode, useEffect } from 'react' import { ErrorBoundary } from 'react-error-boundary' @@ -28,7 +28,7 @@ export const MainLayout: FC = ({ children, subHeader }) => {
    {outOfSync} - + {subHeader} { + const isDesktop = useMediaQuery('(min-width: 1024px)') + const pathname = usePathname() + + const { push } = useRouter() + + const { setSelectedChain, selectedChain, setSelectedDomain } = useDomains() + const { actingAccount } = useWallet() + + const handleDomainSelected = useCallback( + (domain: string) => { + setSelectedDomain(domain) + if (domain === Routes.nova) setSelectedChain(domains[0]) + else setSelectedChain(chains[0]) + push(`/${selectedChain.urls.page}/${domain}`) + }, + [push, setSelectedChain, setSelectedDomain, selectedChain.urls.page], + ) + + const domainIcon = useCallback((domain: (typeof ROUTES)[0], isActive: boolean) => { + const className = `w-6 h-6 ${isActive ? 'text-white' : 'text-grayDark'} dark:text-white` + switch (domain.name) { + case Routes.nova: + return + case Routes.consensus: + return + case Routes.leaderboard: + return + case Routes.staking: + return + default: + return null + } + }, []) + + const domainsOptions = useMemo( + () => + ROUTES.map((item, index) => { + const isActive = pathname.includes(`${selectedChain.urls.page}/${item.name}`) + return ( +
    + + + +
    + ) + }), + [handleDomainSelected, isDesktop, pathname, selectedChain.urls.page, domainIcon], + ) + + return ( +
    +
    +
    {domainsOptions}
    +
    + {!actingAccount ? ( + + ) : ( +
    + + +
    + )} +
    +
    +
    + ) +} diff --git a/explorer/src/constants/metadata.ts b/explorer/src/constants/metadata.ts index 97e18e727..d60959877 100644 --- a/explorer/src/constants/metadata.ts +++ b/explorer/src/constants/metadata.ts @@ -3,7 +3,7 @@ const organization = 'Subspace Labs' const description = 'Subspace Labs Gemini Block Explorer' const keywords = 'Subspace, Subspace Network, Subspace Explorer, Subspace Labs, Subspace Labs Gemini, Subspace Labs Gemini Block Explorer' -export const url = 'https://explorer.subspace.network' +export const url = process.env.NEXTAUTH_URL || 'https://explorer.subspace.network' const twitter = '@SubspaceLabs' const images = { url: url + '/images/share.png', diff --git a/explorer/src/constants/routes.ts b/explorer/src/constants/routes.ts index dc2d121de..a64087e3e 100644 --- a/explorer/src/constants/routes.ts +++ b/explorer/src/constants/routes.ts @@ -1,8 +1,11 @@ export enum Routes { - nova = 'nova', consensus = 'consensus', - leaderboard = 'leaderboard', + farming = 'farming', staking = 'staking', + leaderboard = 'leaderboard', + domains = 'domains', + nova = 'nova', + autoid = 'autoid', // Route deactivated till bugs are fixed and feature is ready // stake = 'stake', } @@ -13,16 +16,30 @@ export const ROUTES = [ title: 'Consensus Chain', }, { - name: Routes.nova, - title: 'Nova', + name: Routes.farming, + title: 'Farming', + }, + { + name: Routes.staking, + title: 'Staking', }, { name: Routes.leaderboard, title: 'Leaderboard', }, { - name: Routes.staking, - title: 'Staking', + name: Routes.domains, + title: 'Domains', + children: [ + { + name: Routes.nova, + title: 'Nova', + }, + { + name: Routes.autoid, + title: 'Auto-ID', + }, + ], }, // Route deactivated till bugs are fixed and feature is ready // { @@ -32,12 +49,12 @@ export const ROUTES = [ ] export const EXTERNAL_ROUTES = { - autonomys: 'https://autonomys.net/', - academy: 'https://academy.autonomys.net/', + autonomys: 'https://autonomys.xyz/', + academy: 'https://academy.autonomys.xyz/', subspacePrivacyPolicy: 'https://subspace.network/gdpr-privacy-statement', - forum: 'https://forum.subspace.network/', + forum: 'https://forum.autonomys.xyz/', gemini2guide: - 'https://forum.subspace.network/t/how-to-check-your-balance-for-gemini-ii-incentivized-testnet/1081', + 'https://forum.autonomys.xyz/t/how-to-check-your-balance-for-gemini-ii-incentivized-testnet/1081', docs: 'https://docs.subspace.network/', operatorDocs: 'https://docs.subspace.network/docs/farming-&-staking/staking/operators/register-operator', @@ -48,7 +65,7 @@ export const EXTERNAL_ROUTES = { github: 'https://github.com/autonomys', reddit: 'https://www.reddit.com/r/sub', medium: 'https://medium.com/subspace-network', - youtube: 'https://www.youtube.com/channel/UCojYRCZOtVTJHJXivOYJzeQ', + youtube: 'https://www.youtube.com/@AutonomysNetwork', linkedin: 'https://www.linkedin.com/company/autonomys/', subSocial: 'https://app.subsocial.network/@NetworkSubspace', }, @@ -56,6 +73,7 @@ export const EXTERNAL_ROUTES = { polkadot: 'https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Frpc-0.gemini-3h.subspace.network%2Fws#/explorer', subscan: 'https://subspace.subscan.io/', + spaceAcres: 'https://api.github.com/repos/subspace/space-acres/releases/latest', } export const INTERNAL_ROUTES = { diff --git a/explorer/src/providers/WalletProvider.tsx b/explorer/src/providers/WalletProvider.tsx index f3bea90ae..24b78c265 100644 --- a/explorer/src/providers/WalletProvider.tsx +++ b/explorer/src/providers/WalletProvider.tsx @@ -78,9 +78,9 @@ export const WalletProvider: FC = ({ children }) => { async (account: WalletAccountWithType) => { try { const type = - account.type === WalletType.ethereum - ? WalletType.ethereum - : WalletType.subspace || WalletType.subspace + account.type === WalletType.subspace || (account as { type: string }).type === 'sr25519' + ? WalletType.subspace + : WalletType.ethereum setActingAccount({ ...account, type, diff --git a/explorer/src/utils/string.ts b/explorer/src/utils/string.ts index e8e9f129f..bca19abf7 100644 --- a/explorer/src/utils/string.ts +++ b/explorer/src/utils/string.ts @@ -5,3 +5,6 @@ export const camelToNormal = (text: string) => text.replace(/([A-Z])/g, ' $1').t export const capitalizeFirstLetter = (string: string) => string ? string.charAt(0).toUpperCase() + string.slice(1) : '' + +export const limitText = (text: string, limit = 20) => + text.length > limit ? text.slice(0, limit) + '...' : text