diff --git a/packages/frontend/.env b/packages/frontend/.env index f2dda9ed6..641a67225 100644 --- a/packages/frontend/.env +++ b/packages/frontend/.env @@ -1,2 +1,13 @@ NEXT_PUBLIC_API_URL=https://testnet.platform-explorer.pshenmic.dev NEXT_PUBLIC_BASE_URL=https://testnet.platform-explorer.com + +NEXT_PUBLIC_MAINNET_BASE_URL=https://platform-explorer.com +NEXT_PUBLIC_TESTNET_BASE_URL=https://testnet.platform-explorer.com + +NEXT_PUBLIC_MAINNET_INSIGHT_URL=https://insight.dash.org/insight +NEXT_PUBLIC_TESTNET_INSIGHT_URL=http://insight.testnet.networks.dash.org/insight + +NEXT_PUBLIC_MAINNET_PLATFORM_EXPLORER_DATA_CONTRACT_IDENTITY='' +NEXT_PUBLIC_TESTNET_PLATFORM_EXPLORER_DATA_CONTRACT_IDENTITY=49GmYeGAk9s6nnuFLJd1KXVKoXtsvA7WdGacGHDAMbLJ + +NEXT_PUBLIC_VOTING_DATA_CONTRACT_ID=GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec \ No newline at end of file diff --git a/packages/frontend/src/app/Providers.js b/packages/frontend/src/app/Providers.js deleted file mode 100644 index 5d222ad4d..000000000 --- a/packages/frontend/src/app/Providers.js +++ /dev/null @@ -1,14 +0,0 @@ -'use client' - -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { NuqsAdapter } from 'nuqs/adapters/next/app' - -const queryClient = new QueryClient() - -export const Providers = ({ children }) => { - return ( - - {children} - - ) -} diff --git a/packages/frontend/src/app/block/[hash]/Block.js b/packages/frontend/src/app/block/[hash]/Block.js index b32112dc8..e2827bb00 100644 --- a/packages/frontend/src/app/block/[hash]/Block.js +++ b/packages/frontend/src/app/block/[hash]/Block.js @@ -10,8 +10,9 @@ import { Container, Tabs, TabList, Tab, TabPanels, TabPanel } from '@chakra-ui/r import { BlockDigestCard, BlockTotalCard, QuorumMembersList } from '../../../components/blocks' import { useBreadcrumbs } from '../../../contexts/BreadcrumbsContext' import { usePathname, useRouter, useSearchParams } from 'next/navigation' -import { networks } from '../../../constants/networks' import { QuorumInfo } from '../../../components/blocks/quorum' +import { useActiveNetwork } from 'src/contexts' + import './Block.scss' const tabs = [ @@ -28,9 +29,7 @@ function Block ({ hash }) { const [rate, setRate] = useState({ data: {}, loading: true, error: false }) const [status, setStatus] = useState({ data: {}, loading: true, error: false }) const [activeTab, setActiveTab] = useState(tabs.indexOf(defaultTabName.toLowerCase()) !== -1 ? tabs.indexOf(defaultTabName.toLowerCase()) : 0) - const baseUrl = process.env.NEXT_PUBLIC_BASE_URL - const activeNetwork = networks.find(network => network.explorerBaseUrl === baseUrl) - const l1explorerBaseUrl = activeNetwork?.l1explorerBaseUrl || null + const { l1explorerBaseUrl } = useActiveNetwork() const router = useRouter() const pathname = usePathname() const searchParams = useSearchParams() diff --git a/packages/frontend/src/app/dataContracts/DataContracts.js b/packages/frontend/src/app/dataContracts/DataContracts.js index 6265f6e36..512f9965b 100644 --- a/packages/frontend/src/app/dataContracts/DataContracts.js +++ b/packages/frontend/src/app/dataContracts/DataContracts.js @@ -8,6 +8,7 @@ import PageSizeSelector from '../../components/pageSizeSelector/PageSizeSelector import { useQuery } from '@tanstack/react-query' import { useQueryState, parseAsInteger } from 'nuqs' import { normalizePagination } from '@utils/table' + import { Container, Heading, diff --git a/packages/frontend/src/app/layout.js b/packages/frontend/src/app/layout.js index 429ce0179..ab924f20a 100644 --- a/packages/frontend/src/app/layout.js +++ b/packages/frontend/src/app/layout.js @@ -1,5 +1,13 @@ import RootComponent from '../components/layout/RootComponent' -import { Providers } from './Providers' +import { + Montserrat, + Open_Sans as OpenSans, + Roboto_Mono as RobotoMono +} from 'next/font/google' + +const montserrat = Montserrat({ subsets: ['latin'], variable: '--font-montserrat' }) +const openSans = OpenSans({ subsets: ['latin'], variable: '--font-open-sans' }) +const robotoMono = RobotoMono({ subsets: ['latin'], variable: '--font-roboto-mono' }) export const viewport = { width: 'device-width', @@ -13,11 +21,10 @@ export default function RootLayout ({ children }) { lang='en' data-theme='dark' style={{ colorScheme: 'dark' }} - > + className={`${montserrat.variable} ${robotoMono.variable} ${openSans.variable}`} + > - - {children} - + {children} ) diff --git a/packages/frontend/src/app/transaction/[hash]/components/PayoutAddress.js b/packages/frontend/src/app/transaction/[hash]/components/PayoutAddress.js index dbf1d63d4..b008af8ea 100644 --- a/packages/frontend/src/app/transaction/[hash]/components/PayoutAddress.js +++ b/packages/frontend/src/app/transaction/[hash]/components/PayoutAddress.js @@ -3,18 +3,14 @@ import { ValueCard } from '@components/cards' import { useEffect, useState } from 'react' import * as Api from '@utils/Api' import { fetchHandlerSuccess, fetchHandlerError } from '@utils' -import { networks } from '../../../../constants/networks' import { ValueContainer } from '@components/ui/containers' +import { useActiveNetwork } from 'src/contexts' import styles from './PayoutAddress.module.scss' -const baseUrl = process.env.NEXT_PUBLIC_BASE_URL -const activeNetwork = networks.find( - (network) => network.explorerBaseUrl === baseUrl -) -const l1explorerBaseUrl = activeNetwork?.l1explorerBaseUrl || null - export const PayoutAddress = ({ outputScript, loading, identity }) => { + const { l1explorerBaseUrl } = useActiveNetwork() + const [validator, setValidator] = useState({ data: {}, loading: true, diff --git a/packages/frontend/src/app/transaction/[hash]/components/Transaction.js b/packages/frontend/src/app/transaction/[hash]/components/Transaction.js index 3352aa354..63c874634 100644 --- a/packages/frontend/src/app/transaction/[hash]/components/Transaction.js +++ b/packages/frontend/src/app/transaction/[hash]/components/Transaction.js @@ -21,20 +21,15 @@ import { TransactionStatusBadge } from '../../../../components/transactions' import { ErrorMessageBlock } from '../../../../components/Errors' -import { networks } from '../../../../constants/networks' import { useBreadcrumbs } from '../../../../contexts/BreadcrumbsContext' import { useDecodedSTQuery, useRateQuery, useTransactionQuery } from './hooks' import { useParams } from 'next/navigation' +import { useActiveNetwork } from 'src/contexts' import './transaction.scss' -const baseUrl = process.env.NEXT_PUBLIC_BASE_URL -const activeNetwork = networks.find( - (network) => network.explorerBaseUrl === baseUrl -) -const l1explorerBaseUrl = activeNetwork?.l1explorerBaseUrl || null - export const Transaction = () => { + const { l1explorerBaseUrl } = useActiveNetwork() const { hash } = useParams() const { setBreadcrumbs } = useBreadcrumbs() const transaction = useTransactionQuery() diff --git a/packages/frontend/src/app/transaction/[hash]/components/TransactionType/AssetLockProof.js b/packages/frontend/src/app/transaction/[hash]/components/TransactionType/AssetLockProof.js index 9d8a3e305..050804f67 100644 --- a/packages/frontend/src/app/transaction/[hash]/components/TransactionType/AssetLockProof.js +++ b/packages/frontend/src/app/transaction/[hash]/components/TransactionType/AssetLockProof.js @@ -1,19 +1,16 @@ -import { networks } from '../../../../../constants/networks' import { CopyButton } from '../../../../../components/ui/Buttons' import { InfoLine, Identifier } from '@components/data' import { ValueCard } from '@components/cards' import { ValueContainer } from '@components/ui/containers' - -const baseUrl = process.env.NEXT_PUBLIC_BASE_URL -const activeNetwork = networks.find( - (network) => network.explorerBaseUrl === baseUrl -) -const l1explorerBaseUrl = activeNetwork?.l1explorerBaseUrl || null +import { useActiveNetwork } from 'src/contexts' export const AssetLockProof = ({ assetLockProof: { fundingCoreTx, instantLock }, loading -}) => ( +}) => { + const { l1explorerBaseUrl } = useActiveNetwork() + + return ( <> {instantLock && ( )} -) + ) +} diff --git a/packages/frontend/src/app/transaction/[hash]/components/TransactionType/TokenConfiguration/DistType.module.scss b/packages/frontend/src/app/transaction/[hash]/components/TransactionType/TokenConfiguration/DistType.module.scss index 07e444b9e..8c5c7fa52 100644 --- a/packages/frontend/src/app/transaction/[hash]/components/TransactionType/TokenConfiguration/DistType.module.scss +++ b/packages/frontend/src/app/transaction/[hash]/components/TransactionType/TokenConfiguration/DistType.module.scss @@ -17,7 +17,7 @@ .container { width: fit-content; padding: 6px 10px; - background-color: #2e393d; + background-color: var(--chakra-colors-gray-800); .title { font-family: $font-mono; diff --git a/packages/frontend/src/app/validator/[hash]/Validator.js b/packages/frontend/src/app/validator/[hash]/Validator.js index 2f0b39c49..ae0b90c86 100644 --- a/packages/frontend/src/app/validator/[hash]/Validator.js +++ b/packages/frontend/src/app/validator/[hash]/Validator.js @@ -17,7 +17,6 @@ import { HorisontalSeparator } from '../../../components/ui/separators' import { ValidatorCard } from '../../../components/validators' import { CircleIcon } from '../../../components/ui/icons' import { RateTooltip } from '../../../components/ui/Tooltips' -import { networks } from '../../../constants/networks' import { WithdrawalsList } from '../../../components/transfers' import { useBreadcrumbs } from '../../../contexts/BreadcrumbsContext' import { defaultChartConfig } from '../../../components/charts/config' @@ -25,6 +24,8 @@ import { Badge, Tabs, TabList, TabPanels, Tab, TabPanel } from '@chakra-ui/react' +import { useActiveNetwork } from 'src/contexts' + import './ValidatorPage.scss' function Validator ({ hash }) { @@ -37,9 +38,7 @@ function Validator ({ hash }) { const [transactions, setTransactions] = useState({ data: {}, props: { currentPage: 0 }, loading: true, error: false }) const [withdrawals, setWithdrawals] = useState({ data: {}, props: { currentPage: 0 }, loading: true, error: false }) const [activeChartTab, setActiveChartTab] = useState(0) - const baseUrl = process.env.NEXT_PUBLIC_BASE_URL - const activeNetwork = networks.find(network => network.explorerBaseUrl === baseUrl) - const l1explorerBaseUrl = activeNetwork?.l1explorerBaseUrl || null + const { l1explorerBaseUrl } = useActiveNetwork() const [timespan, setTimespan] = useState(defaultChartConfig.timespan.values[defaultChartConfig.timespan.defaultIndex]) useEffect(() => { diff --git a/packages/frontend/src/components/blocks/BlocksListItem.js b/packages/frontend/src/components/blocks/BlocksListItem.js index 339cd8741..b97329775 100644 --- a/packages/frontend/src/components/blocks/BlocksListItem.js +++ b/packages/frontend/src/components/blocks/BlocksListItem.js @@ -6,6 +6,7 @@ import { Badge, Grid, GridItem } from '@chakra-ui/react' import { BlockIcon } from '../ui/icons' import { LinkContainer } from '../ui/containers' import { useRouter } from 'next/navigation' + import './BlocksListItem.scss' function BlocksListItem ({ block, absoluteDate }) { diff --git a/packages/frontend/src/components/dataContracts/DataContractModal/DataContractModal.js b/packages/frontend/src/components/dataContracts/DataContractModal/DataContractModal.js new file mode 100644 index 000000000..b126a677d --- /dev/null +++ b/packages/frontend/src/components/dataContracts/DataContractModal/DataContractModal.js @@ -0,0 +1,39 @@ +import { useEffect, useState } from 'react' +import { FORM_MODE_ENUM } from './constants' +import { NameScreen } from './NameScreen' +import { InitialScreen } from './InitialScreen' +import { KeywordsScreen } from './KeywordsScreen' +import { Modal } from '@components/ui/Modal' + +const MODE_PROPS = { + [FORM_MODE_ENUM.INITIAL]: { + title: 'Edit Data Contract Information', + Content: InitialScreen + }, + [FORM_MODE_ENUM.NAME_EDIT]: { + title: 'Edit Data Contract Name', + Content: NameScreen + }, + [FORM_MODE_ENUM.KEYWORDS_EDIT]: { + title: 'Edit Data Contract Description and Keywords', + Content: KeywordsScreen + } +} + +export const DataContractModal = ({ isOpen, ...props }) => { + const [mode, setMode] = useState(FORM_MODE_ENUM.INITIAL) + const { title, Content } = MODE_PROPS[mode] + + useEffect(() => { + setMode(FORM_MODE_ENUM.INITIAL) + }, [isOpen]) + + return ( + + + + ) +} diff --git a/packages/frontend/src/components/dataContracts/DataContractModal/InitialScreen.js b/packages/frontend/src/components/dataContracts/DataContractModal/InitialScreen.js new file mode 100644 index 000000000..fce0c590e --- /dev/null +++ b/packages/frontend/src/components/dataContracts/DataContractModal/InitialScreen.js @@ -0,0 +1,41 @@ +import { SignatureIcon, DocumentIcon } from '@components/ui/icons' +import { FORM_MODE_ENUM } from './constants' + +import styles from './InitialScreen.module.scss' + +export const InitialScreen = ({ setMode }) => ( +
+ + Select what information you would like to edit. You can change Data + Contract Name or Description with Keywords. Notice you can only change the + name once every 15 minutes + + +
+ + +
+
+) diff --git a/packages/frontend/src/components/dataContracts/DataContractModal/InitialScreen.module.scss b/packages/frontend/src/components/dataContracts/DataContractModal/InitialScreen.module.scss new file mode 100644 index 000000000..1749f99e8 --- /dev/null +++ b/packages/frontend/src/components/dataContracts/DataContractModal/InitialScreen.module.scss @@ -0,0 +1,52 @@ +@import 'variables'; + +.root { + .description { + font-family: $font-mono; + color: var(--chakra-colors-gray-250); + font-weight: 400; + font-size: 12px; + line-height: 110%; + } + + .controls { + margin-top: 24px; + display: flex; + gap: 12px; + + .card { + max-width: 294px; + width: 100%; + height: 125px; + background-color: var(--chakra-colors-gray-800); + border-radius: 24px; + border: #2e393d80 solid 1px; + padding: 24px 24px 27px; + display: flex; + flex-direction: column; + gap: 24px; + transition: background-color ease-in .2; + + &:hover { + background-color: var(--chakra-colors-gray-700); + } + + &:active { + background-color: var(--chakra-colors-gray-600); + } + + .name { + width: fit-content; + font-family: $font-mono; + font-weight: 400; + font-size: 12px; + line-height: 130%; + + .mark { + font-weight: 400; + color: $color-brand-normal; + } + } + } + } +} diff --git a/packages/frontend/src/components/dataContracts/DataContractModal/KeywordsScreen.js b/packages/frontend/src/components/dataContracts/DataContractModal/KeywordsScreen.js new file mode 100644 index 000000000..9f531d58a --- /dev/null +++ b/packages/frontend/src/components/dataContracts/DataContractModal/KeywordsScreen.js @@ -0,0 +1,131 @@ +import { Divider, Input, Textarea, FormControl } from '@chakra-ui/react' +import { useId, useState } from 'react' +import { cva } from 'class-variance-authority' + +import styles from './KeywordsScreen.module.scss' + +const button = cva([styles.btn]) + +export const KeywordsScreen = ({ onChangeDescription }) => { + const [form, setForm] = useState({ description: '', keywords: '' }) + const [errors, setErrors] = useState({ keywords: null }) + const id = useId() + + const validateKeywords = (value) => { + if (!value.trim()) { + return null + } + + const keywordsRegex = /^[a-zA-Zа-яА-Я0-9]+(\s*,\s*[a-zA-Zа-яА-Я0-9]+)*$/ + + if (!keywordsRegex.test(value)) { + return 'Please enter words separated by commas only' + } + + const keywordsArray = value.split(',').map(item => item.trim()) + if (keywordsArray.some(item => item === '')) { + return 'Please remove empty entries between commas' + } + + return null + } + + const handleKeywordsChange = (value) => { + setForm((prev) => ({ ...prev, keywords: value })) + + const error = validateKeywords(value) + setErrors((prev) => ({ ...prev, keywords: error })) + } + + const handleSubmit = (e) => { + e.preventDefault() + + const keywordsError = validateKeywords(form.keywords) + + if (keywordsError) { + setErrors((prev) => ({ ...prev, keywords: keywordsError })) + return + } + + const keywordsArray = form.keywords + .split(',') + .map(item => item.trim()) + .filter(item => item.length > 0) + + const formData = { + description: form.description, + keywords: keywordsArray + } + + onChangeDescription(formData) + } + + return ( +
+
+ +
+
+
+ + +