diff --git a/.prettierignore b/.prettierignore index ee6d21ae..7d2ed38c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,7 +2,6 @@ node_modules/ coverage/ dist/ build/ -preact/ internals/ docs/ gql/ diff --git a/README.md b/README.md index 1e800895..fb30aa39 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,30 @@ # Pendulum Portal -[![Netlify Status](https://api.netlify.com/api/v1/badges/aa69406a-f4a1-4693-aed0-8478f1d1fabd/deploy-status)](https://app.netlify.com/sites/pendulum-portal-alpha/deploys) -  ![TypeScript](https://img.shields.io/badge/-TypeSript-05122A?style=flat&logo=typescript)  -![Preact](https://img.shields.io/badge/-Preact-05122A?style=flat&logo=preact)  -![Vite](https://img.shields.io/badge/-Vite-05122A?style=flat&logo=vite)  -![Tailwind CSS](https://img.shields.io/badge/-Tailwind-05122A?style=flat&logo=tailwindcss)  +[![Netlify Status](https://api.netlify.com/api/v1/badges/aa69406a-f4a1-4693-aed0-8478f1d1fabd/deploy-status)](https://app.netlify.com/sites/pendulum-portal-alpha/deploys)  + +![React](https://img.shields.io/badge/-React-05122A?style=flat&logo=react) +![TypeScript](https://img.shields.io/badge/-TypeSript-05122A?style=flat&logo=typescript) +![Vite](https://img.shields.io/badge/-Vite-05122A?style=flat&logo=vite) ![Polkadot](https://img.shields.io/badge/-Polkadot-05122A?style=flat&logo=polkadot)  +An open-source repository for the [Pendulum Portal](https://portal.pendulumchain.org/), a frontend platform for +interacting with Pendulum Chain. + --- -Web application for Pendulum. This project was bootstrapped with [vite](https://vite.new/preact-ts) +## Table of Contents -## Run +- [Run](#run) +- [Build](#build) +- [Troubleshooting](#troubleshooting) + - [Missing Price Information](#missing-price-information) + - [Fixing Type Issues](#fixing-type-issues) + +--- -In the project directory, you can run: +### Run -### Install yarn - corepack +#### Install yarn with corepack Enable Corepack by executing the command `corepack enable`. Corepack is included by default in Node.js, it manages the Yarn version specified in the `packageManager` field. @@ -26,37 +35,40 @@ via npm - use Corepack instead. **Note:** If you are using Volta to manage your Node.js versions, you need to follow the instructions [here](https://yarnpkg.com/corepack#volta). -### `yarn install` +##### `yarn install` Install dependencies -### `yarn dev` +##### `yarn dev` -Runs the app in development mode.\ -Open [http://127.0.0.1:5173/](http://127.0.0.1:5173) to view it in the browser. +Runs the app in development mode. -## Build +--- + +### Build ### `yarn build` Builds the app for production to the `dist` folder.\ -It transpiles TypeScript, bundles Preact in production mode, splits and optimizes the builds for the best performance. +It transpiles TypeScript, bundles React in production mode, splits and optimizes the builds for the best performance. The build is minified and the filenames include the hashes.\ We call on `version.cjs` to show the commit version on the sidebar.\ We also create a file, on the fly, a file named `_redirects` that will serve the index.html instead of giving a 404 no matter what URL the browser requests. -## Troubleshooting +--- + +### Troubleshooting -### Missing price information +#### Missing price information If you are missing the price information about the assets on the dashboard page, you are probably experiencing a CORS problem with the batching server. If you want to fetch prices locally, you can use the proxy server available at [pendulum-tools](https://github.com/pendulum-chain/pendulum-tools). Change url in `src/hooks/usePriceFetcher.ts` file to `http://localhost:3000` -### Fixing type issues +#### Fixing type issues If you encounter issues with the IDE not detecting the type overwrites of the `@pendulum-chain/types` package properly, make sure that all the `@polkadot/xxx` packages match the same version used in the types package. It is also important diff --git a/config/babel.jest.cjs b/config/babel.jest.cjs deleted file mode 100644 index 1ff9b3ca..00000000 --- a/config/babel.jest.cjs +++ /dev/null @@ -1,26 +0,0 @@ -const babelJestMd = require('babel-jest'); -const babelJest = babelJestMd.__esModule ? babelJestMd.default : babelJestMd; - -module.exports = babelJest.createTransformer({ - presets: [ - [ - '@babel/preset-typescript', - { - jsxPragma: 'h', - }, - ], - '@babel/preset-env', - ], - plugins: [ - [ - '@babel/plugin-transform-react-jsx', - { - runtime: 'automatic', - importSource: 'preact', - }, - ], - '@babel/plugin-proposal-class-properties', - ], - babelrc: false, - configFile: false, -}); diff --git a/config/styleMock.js b/config/styleMock.js deleted file mode 100644 index f053ebf7..00000000 --- a/config/styleMock.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/index.html b/index.html index 633e71a1..7abf5e84 100644 --- a/index.html +++ b/index.html @@ -13,8 +13,8 @@ /> - -
+ +
diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 22d43d37..00000000 --- a/jest.config.js +++ /dev/null @@ -1,24 +0,0 @@ -export default { - roots: ['/src'], - maxWorkers: 1, - transform: { - '\\.(ts|tsx)?$': 'babel-jest', - }, - testMatch: ['**/?(*.)+(spec|test).{ts,tsx}'], - testEnvironment: 'jsdom', - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - moduleNameMapper: { - '^react$': 'preact/compat', - '^react-dom/test-utils$': 'preact/test-utils', - '^react-dom$': 'preact/compat', - '^react/jsx-runtime$': 'preact/jsx-runtime', - '\\.(css|less|sass|scss)$': '/config/styleMock.js', - '\\.(gif|ttf|eot|svg)$': '/config/fileMock.js', - }, - transform: { - '^.+\\.(js|jsx|ts|tsx|mjs|cjs)$': '/config/babel.jest.cjs', - }, - testPathIgnorePatterns: ['/node_modules/', '/public/'], - transformIgnorePatterns: ['node_modules/(?!@testing-library|preact)', '^.+\\.module\\.(css|sass|scss)$'], - setupFilesAfterEnv: ['/config/setupTests.ts'], -}; diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..82a23209 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,20 @@ +import type { Config } from 'jest'; + +const config: Config = { + roots: ['/src'], + preset: 'ts-jest', + testEnvironment: 'jsdom', + clearMocks: true, + testMatch: ['**/?(*.)+(test).ts'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + setupFilesAfterEnv: ['/config/setupTests.ts'], + moduleNameMapper: { + '\\.(jpg|jpeg|png|gif|webp|svg)$': '/config/fileMock.ts', + '^@/(.*)$': '/src/$1', + }, + transform: { + '^.+\\.(ts|tsx)$': 'ts-jest', + }, +}; + +export default config; diff --git a/package.json b/package.json index f9822ec8..b63f5d1d 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "packageManager": "yarn@4.4.1", "scripts": { "dev": "vite", - "build": "node version.cjs && tsc && vite build && echo '/* /index.html 200' | cat > dist/_redirects", + "build": "tsc && vite build", "preview": "vite preview", "lint": "eslint . --ext .ts,.tsx", "lint:fix": "eslint . --ext .ts,.tsx --fix", @@ -47,6 +47,9 @@ "@tanstack/react-query": "~4.32.6", "@tanstack/react-query-devtools": "~4.32.6", "@tanstack/react-table": "^8.11.2", + "@testing-library/react": "^16.0.1", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", "@walletconnect/modal": "^2.6.2", "@walletconnect/universal-provider": "^2.11.1", "big.js": "^6.2.1", @@ -57,22 +60,20 @@ "lodash": "^4.17.21", "luxon": "^3.5.0", "match-sorter": "^6.3.1", - "preact": "^10.21.0", "qrcode.react": "^3.1.0", + "react": "^18.3.1", "react-daisyui": "^5.0.5", "react-device-detect": "^2.2.3", + "react-dom": "^18.3.1", "react-hook-form": "^7.53.0", "react-lottie": "^1.2.4", - "react-router-dom": "^6.8.1", + "react-router-dom": "^6.27.0", "react-toastify": "^9.1.3", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "yup": "^1.4.0" }, "devDependencies": { - "@babel/core": "^7.23.7", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/preset-env": "^7.23.7", - "@babel/preset-typescript": "^7.24.1", "@graphql-codegen/cli": "^5.0.2", "@graphql-codegen/client-preset": "^4.2.6", "@pendulum-chain/types": "^1.1.1", @@ -80,11 +81,8 @@ "@polkadot/types-codec": "^13.2.1", "@polkadot/types-create": "^13.2.1", "@polkadot/types-known": "^13.2.1", - "@preact/preset-vite": "^2.5.0", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.1.6", - "@testing-library/preact": "^3.2.3", - "@testing-library/preact-hooks": "^1.1.0", "@testing-library/user-event": "^14.5.2", "@types/big.js": "^6.2.2", "@types/jest": "^29.5.11", @@ -97,7 +95,6 @@ "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", "autoprefixer": "^10.4.20", - "babel-jest": "^29.4.3", "daisyui": "^4.12.13", "eslint": "^8.56.0", "eslint-plugin-jest": "^27.6.1", @@ -121,7 +118,8 @@ "yarn": ">=4.0.0" }, "resolutions": { - "@polkadot/api": "^13.2.1" + "@polkadot/api": "^13.2.1", + "@polkadot/util": "^13.2.1" }, "lint-staged": { "*.{ts,tsx}": "eslint --cache --fix", diff --git a/postcss.config.js b/postcss.config.js index 9fbc7ef0..feab3515 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -5,5 +5,4 @@ export default { tailwindcss: {}, autoprefixer: {}, }, - }; diff --git a/src/GlobalStateProvider/index.tsx b/src/GlobalStateProvider/index.tsx index 1f0e648b..b1507854 100644 --- a/src/GlobalStateProvider/index.tsx +++ b/src/GlobalStateProvider/index.tsx @@ -1,5 +1,4 @@ -import { ComponentChildren, createContext } from 'preact'; -import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'preact/compat'; +import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { WalletAccount } from '@talismn/connect-wallets'; import { config } from '../config'; @@ -26,7 +25,7 @@ export interface GlobalState { export const defaultTenant = TenantName.Pendulum; const GlobalStateContext = createContext(undefined); -const GlobalStateProvider = ({ children }: { children: ComponentChildren }) => { +const GlobalStateProvider = ({ children }: { children: JSX.Element }) => { const tenantRef = useRef(); const [walletAccount, setWallet] = useState(undefined); const { pathname } = useLocation(); diff --git a/src/NodeInfoProvider.tsx b/src/NodeInfoProvider.tsx index f5755984..8d958aea 100644 --- a/src/NodeInfoProvider.tsx +++ b/src/NodeInfoProvider.tsx @@ -1,8 +1,7 @@ +import { createContext, Dispatch, useContext, useEffect, useState } from 'react'; import { options } from '@pendulum-chain/api'; import { rpc } from '@pendulum-chain/types'; import { ApiPromise, WsProvider } from '@polkadot/api'; -import { createContext } from 'preact'; -import { useContext, useEffect, useState } from 'preact/hooks'; import { ToastMessage, showToast } from './shared/showToast'; async function createApiPromise(provider: WsProvider) { @@ -29,10 +28,10 @@ export interface NodeInfoProviderInterface { const NodeInfoContext = createContext({ state: {} as Partial, - setState: {} as Dispatch>>, + setState: {} as Dispatch>, }); -const NodeInfoProvider = ({ children, tenantRPC }: { children: ReactNode; tenantRPC?: string }) => { +const NodeInfoProvider = ({ children, tenantRPC }: { children: React.ReactNode; tenantRPC?: string }) => { const [state, setState] = useState({} as Partial); const [currentTenantRPC, setCurrentTenantRPC] = useState(undefined); const [pendingInitiationPromise, setPendingInitiationPromise] = useState | undefined>(undefined); diff --git a/src/SharedProvider.tsx b/src/SharedProvider.tsx index 4e4a7aa4..2ead9117 100644 --- a/src/SharedProvider.tsx +++ b/src/SharedProvider.tsx @@ -1,9 +1,8 @@ -import { ComponentChildren } from 'preact'; import { useGlobalState } from './GlobalStateProvider'; import { useNodeInfoState } from './NodeInfoProvider'; import { SharedStateProvider } from './shared/Provider'; -const SharedProvider = ({ children }: { children: ComponentChildren }) => { +const SharedProvider = ({ children }: { children: JSX.Element }) => { const { api } = useNodeInfoState().state; const { signer, address } = useGlobalState().walletAccount || {}; diff --git a/src/TermsAndConditions.tsx b/src/TermsAndConditions.tsx index e7208c7e..feeef5dc 100644 --- a/src/TermsAndConditions.tsx +++ b/src/TermsAndConditions.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren, useState } from 'preact/compat'; +import { PropsWithChildren, useState } from 'react'; import { Button, Checkbox, Link, Modal } from 'react-daisyui'; import { useLocalStorage, LocalStorageKeys } from './hooks/useLocalStorage'; diff --git a/src/assets/AmplitudeLogo.tsx b/src/assets/AmplitudeLogo.tsx index 7c833373..3b3f4a27 100644 --- a/src/assets/AmplitudeLogo.tsx +++ b/src/assets/AmplitudeLogo.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; interface Props extends HTMLAttributes { className?: string; diff --git a/src/assets/ChainLogo.tsx b/src/assets/ChainLogo.tsx index 6efdac8c..9101ea8c 100644 --- a/src/assets/ChainLogo.tsx +++ b/src/assets/ChainLogo.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; import { useGlobalState } from '../GlobalStateProvider'; import { TenantName } from '../models/Tenant'; import { AmplitudeLogo } from './AmplitudeLogo'; @@ -6,6 +6,8 @@ import { PendulumLogo } from './PendulumLogo'; interface Props extends HTMLAttributes { className?: string; + width?: string; + height?: string; } export const ChainLogo = (props: Props) => { diff --git a/src/assets/CloseIcon.tsx b/src/assets/CloseIcon.tsx index e1e640f8..f0c0d55d 100644 --- a/src/assets/CloseIcon.tsx +++ b/src/assets/CloseIcon.tsx @@ -1,4 +1,4 @@ -import { FC } from 'preact/compat'; +import { FC } from 'react'; interface Props { className?: string; diff --git a/src/assets/ExternalIcon.tsx b/src/assets/ExternalIcon.tsx index 886a2e58..66c8471b 100644 --- a/src/assets/ExternalIcon.tsx +++ b/src/assets/ExternalIcon.tsx @@ -1,4 +1,4 @@ -import { FC } from 'preact/compat'; +import { FC } from 'react'; interface Props { className?: string; diff --git a/src/assets/PendulumLogo.tsx b/src/assets/PendulumLogo.tsx index 439bd21c..e3cfd0c1 100644 --- a/src/assets/PendulumLogo.tsx +++ b/src/assets/PendulumLogo.tsx @@ -3,9 +3,9 @@ interface Props { light?: boolean; } -export const PendulumLogo = ({ className, light }: Props) => +export const PendulumLogo = ({ className, light, ...rest }: Props) => light ? ( - + ( ) => ( diff --git a/src/assets/collators-staked-icon.tsx b/src/assets/collators-staked-icon.tsx index 2013442d..533aa70e 100644 --- a/src/assets/collators-staked-icon.tsx +++ b/src/assets/collators-staked-icon.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; const StakedIcon = (props: HTMLAttributes) => ( diff --git a/src/assets/dashboard.tsx b/src/assets/dashboard.tsx index 148c0d38..bb813a05 100644 --- a/src/assets/dashboard.tsx +++ b/src/assets/dashboard.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; const DashboardIcon = (props: HTMLAttributes) => ( diff --git a/src/assets/governance.tsx b/src/assets/governance.tsx index fe8125c1..8cdef2d1 100644 --- a/src/assets/governance.tsx +++ b/src/assets/governance.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; const GovernanceIcon = (props: HTMLAttributes) => ( diff --git a/src/assets/nabla.tsx b/src/assets/nabla.tsx index ad355cce..b5367b36 100644 --- a/src/assets/nabla.tsx +++ b/src/assets/nabla.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; const NablaIcon = (props: HTMLAttributes) => ( ) => ( diff --git a/src/assets/spacewalk.tsx b/src/assets/spacewalk.tsx index c4c9b848..14559daa 100644 --- a/src/assets/spacewalk.tsx +++ b/src/assets/spacewalk.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; const SpacewalkIcon = (props: HTMLAttributes) => ( diff --git a/src/assets/spinner.tsx b/src/assets/spinner.tsx index e5c6b968..14b6555d 100644 --- a/src/assets/spinner.tsx +++ b/src/assets/spinner.tsx @@ -1,4 +1,4 @@ -import type { HTMLAttributes } from 'preact/compat'; +import type { HTMLAttributes } from 'react'; export type SpinnerProps = { size?: number; diff --git a/src/assets/staking.tsx b/src/assets/staking.tsx index 3e690929..df1d3850 100644 --- a/src/assets/staking.tsx +++ b/src/assets/staking.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; const StakingIcon = (props: HTMLAttributes) => ( diff --git a/src/assets/swap.tsx b/src/assets/swap.tsx index 315415f1..7f180ed6 100644 --- a/src/assets/swap.tsx +++ b/src/assets/swap.tsx @@ -1,4 +1,4 @@ -import { HTMLAttributes } from 'preact/compat'; +import { HTMLAttributes } from 'react'; const SwapIcon = (props: HTMLAttributes) => ( diff --git a/src/components/Asset/Badge/index.tsx b/src/components/Asset/Badge/index.tsx index e011b549..631c5153 100644 --- a/src/components/Asset/Badge/index.tsx +++ b/src/components/Asset/Badge/index.tsx @@ -9,7 +9,7 @@ const sizes = { export type BadgeProps = { size?: keyof typeof sizes; className?: string; - children?: ReactNode; + children?: React.ReactNode; }; const AssetBadge = ({ size = 'md', className, children }: BadgeProps): JSX.Element | null => { diff --git a/src/components/Asset/Price/index.tsx b/src/components/Asset/Price/index.tsx index ee4c03b4..faf30424 100644 --- a/src/components/Asset/Price/index.tsx +++ b/src/components/Asset/Price/index.tsx @@ -1,16 +1,16 @@ import { UseQueryOptions } from '@tanstack/react-query'; import { SpacewalkPrimitivesCurrencyId } from '@polkadot/types/lookup'; -import { memo, useEffect, useState } from 'preact/compat'; +import { memo, useEffect, useState } from 'react'; import { usePriceFetcher } from '../../../hooks/usePriceFetcher'; import { NumberLoader } from '../../Loader'; export type TokenPriceProps = { address?: string; currency: SpacewalkPrimitivesCurrencyId; - prefix?: ReactNode; + prefix?: React.ReactNode; options?: UseQueryOptions; - loader?: ReactNode; - fallback?: ReactNode; + loader?: React.ReactNode; + fallback?: React.ReactNode; }; const TokenPrice = memo(({ currency, prefix = null, loader, fallback = null }: TokenPriceProps): JSX.Element | null => { diff --git a/src/components/Box/index.test.tsx b/src/components/Box/index.test.tsx deleted file mode 100644 index 3039ac44..00000000 --- a/src/components/Box/index.test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { render, screen } from '@testing-library/preact'; -import { Fragment } from 'preact'; -import Box from '.'; - -describe('Box', () => { - test('should display initial count', () => { - const title = 'Test title'; - const children = 'Test children'; - render( - - {children} - , - ); - expect(screen.getByText(title)).toBeInTheDocument(); - expect(screen.getByText(children)).toBeInTheDocument(); - }); -}); diff --git a/src/components/Box/index.tsx b/src/components/Box/index.tsx deleted file mode 100644 index f9c1dcf1..00000000 --- a/src/components/Box/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { ComponentChildren } from 'preact'; -import './styles.css'; - -type Props = { - title?: string; - subTitle?: string; - children?: ComponentChildren; -}; - -const Box = ({ title, subTitle, children }: Props) => { - return ( -
-
- {title &&

{title}

} - {subTitle &&

{subTitle}

} - {children} -
-
- ); -}; - -export default Box; diff --git a/src/components/Box/styles.css b/src/components/Box/styles.css deleted file mode 100644 index 21ccf9e6..00000000 --- a/src/components/Box/styles.css +++ /dev/null @@ -1,20 +0,0 @@ -.box { - width: 100%; - border-radius: 30px; - box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2); - background-color: var(--clear); - position: relative; - min-height: auto; -} - -.box h2 { - font-size: 30px; - font-weight: 700; - padding: 20px; -} - -.box h1 { - font-weight: 500; - font-size: 18px; - padding: 20px; -} diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index b4a57271..1b82063f 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -1,5 +1,6 @@ import { Modal } from 'react-daisyui'; -import { createPortal, useCallback, useEffect, useRef, useState } from 'preact/compat'; +import { FormEvent, useCallback, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; import { CloseButton } from '../CloseButton'; interface DialogProps { @@ -9,7 +10,7 @@ interface DialogProps { content: JSX.Element; actions?: JSX.Element; form?: { - onSubmit: (event?: Event) => void; + onSubmit: (event?: FormEvent) => void; className?: string; }; id?: string; @@ -59,7 +60,7 @@ export function Dialog({ visible, onClose, headerText, content, actions, id, for } }, [visible, closeListener, headerText]); - const handleFormSubmit = (event: Event) => { + const handleFormSubmit = (event: FormEvent) => { if (form) { setIsSubmitting(true); event.preventDefault(); @@ -73,7 +74,7 @@ export function Dialog({ visible, onClose, headerText, content, actions, id, for const modalBody = ( <> {content} - {actions} + {actions} ); @@ -83,7 +84,7 @@ export function Dialog({ visible, onClose, headerText, content, actions, id, for {headerText} {form ? ( -
+ {modalBody}
) : ( diff --git a/src/components/DropdownSelector/index.tsx b/src/components/DropdownSelector/index.tsx index f6346e35..81b060a9 100644 --- a/src/components/DropdownSelector/index.tsx +++ b/src/components/DropdownSelector/index.tsx @@ -27,7 +27,7 @@ interface Props { value?: T; } -function DropdownSelector(props: Props) { +function DropdownSelector(props: Props) { const { items, onChange, value } = props; return ( diff --git a/src/components/Form/From/Badges/index.tsx b/src/components/Form/From/Badges/index.tsx index 6b3dae59..f181bbb1 100644 --- a/src/components/Form/From/Badges/index.tsx +++ b/src/components/Form/From/Badges/index.tsx @@ -1,4 +1,4 @@ -import { FC } from 'preact/compat'; +import { FC } from 'react'; import useSwitchChain from '../../../../hooks/useSwitchChain'; import { TenantName } from '../../../../models/Tenant'; diff --git a/src/components/Form/From/NumericInput/NumericInput.test.tsx b/src/components/Form/From/NumericInput/NumericInput.test.tsx index 1c0944c2..07bdf3a3 100644 --- a/src/components/Form/From/NumericInput/NumericInput.test.tsx +++ b/src/components/Form/From/NumericInput/NumericInput.test.tsx @@ -1,9 +1,9 @@ -import { h } from 'preact'; import { UseFormRegisterReturn } from 'react-hook-form'; -import { render } from '@testing-library/preact'; +import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { NumericInput } from '.'; import { handleOnPasteNumericInput } from './helpers'; +import { ClipboardEvent } from 'react'; const mockRegister: UseFormRegisterReturn = { name: 'testInput', diff --git a/src/components/Form/From/NumericInput/helpers.ts b/src/components/Form/From/NumericInput/helpers.ts index 2c7f4514..d29f7f9b 100644 --- a/src/components/Form/From/NumericInput/helpers.ts +++ b/src/components/Form/From/NumericInput/helpers.ts @@ -1,3 +1,4 @@ +import { ChangeEvent, ClipboardEvent, KeyboardEvent } from 'react'; import { trimToMaxDecimals } from '../../../../shared/parseNumbers/maxDecimals'; const removeNonNumericCharacters = (value: string): string => value.replace(/[^0-9.]/g, ''); @@ -17,7 +18,7 @@ const replaceCommasWithDots = (value: string): string => value.replace(/,/g, '.' * @param e - The keyboard event triggered by the input. * @param maxDecimals - The maximum number of decimal places allowed. */ -export function handleOnChangeNumericInput(e: KeyboardEvent, maxDecimals: number): void { +export function handleOnChangeNumericInput(e: ChangeEvent, maxDecimals: number): void { const target = e.target as HTMLInputElement; target.value = replaceCommasWithDots(target.value); diff --git a/src/components/Form/From/NumericInput/index.tsx b/src/components/Form/From/NumericInput/index.tsx index 2fd14e29..579630d9 100644 --- a/src/components/Form/From/NumericInput/index.tsx +++ b/src/components/Form/From/NumericInput/index.tsx @@ -3,6 +3,7 @@ import { UseFormRegisterReturn } from 'react-hook-form'; import { USER_INPUT_MAX_DECIMALS } from '../../../../shared/parseNumbers/maxDecimals'; import { handleOnChangeNumericInput, handleOnKeyDownNumericInput, handleOnPasteNumericInput } from './helpers'; +import { ChangeEvent, ClipboardEvent } from 'react'; interface NumericInputProps { register: UseFormRegisterReturn; @@ -21,7 +22,7 @@ export const NumericInput = ({ defaultValue, autoFocus, }: NumericInputProps) => { - function handleOnChange(e: KeyboardEvent): void { + function handleOnChange(e: ChangeEvent): void { handleOnChangeNumericInput(e, maxDecimals); register.onChange(e); } @@ -32,28 +33,28 @@ export const NumericInput = ({ } return ( -
-
+
+
diff --git a/src/components/Form/From/index.tsx b/src/components/Form/From/index.tsx index 4b82dcfd..53a66d2d 100644 --- a/src/components/Form/From/index.tsx +++ b/src/components/Form/From/index.tsx @@ -1,5 +1,5 @@ import { UseFormRegisterReturn } from 'react-hook-form'; -import { StateUpdater, Dispatch } from 'preact/hooks'; +import { Dispatch } from 'react'; import { Asset } from '@stellar/stellar-sdk'; import { BlockchainAsset } from '../../Selector/AssetSelector/helpers'; @@ -23,7 +23,7 @@ export interface FromProps { asset: { assets?: BlockchainAsset[]; selectedAsset?: BlockchainAsset; - setSelectedAsset?: Dispatch> | Dispatch>; + setSelectedAsset?: Dispatch | Dispatch; assetSuffix?: string; }; description: { diff --git a/src/components/Form/From/variants/StandardFrom.test.tsx b/src/components/Form/From/variants/StandardFrom.test.tsx index 5aac21e9..35aad7ff 100644 --- a/src/components/Form/From/variants/StandardFrom.test.tsx +++ b/src/components/Form/From/variants/StandardFrom.test.tsx @@ -1,7 +1,7 @@ import '@testing-library/jest-dom'; import Big from 'big.js'; import userEvent from '@testing-library/user-event'; -import { render } from '@testing-library/preact'; +import { render } from '@testing-library/react'; import { SpacewalkPrimitivesCurrencyId } from '@polkadot/types/lookup'; import { useForm } from 'react-hook-form'; diff --git a/src/components/Form/Validation/index.tsx b/src/components/Form/Validation/index.tsx index 2faa5f79..c8ee6ce2 100644 --- a/src/components/Form/Validation/index.tsx +++ b/src/components/Form/Validation/index.tsx @@ -1,17 +1,21 @@ -import { FieldErrors } from 'react-hook-form'; +import { FieldError, FieldErrors } from 'react-hook-form'; export interface ValidationProps { errors?: FieldErrors; className?: string; } -const Validation = ({ errors = {}, className }: ValidationProps): JSX.Element | null => { - const keys = Object.keys(errors); - if (keys.length === 0) return null; +export const Validation = ({ errors = {}, className = '' }: ValidationProps) => { + const errorEntries = Object.entries(errors); + + if (errorEntries.length === 0) return <>; + return (
    - {keys.map((key, i) => (errors[key] && errors[key]?.message ?
  • {errors[key]?.message}
  • : null))} + {errorEntries.map(([field, error]) => { + const message = (error as FieldError)?.message; + return typeof message === 'string' ?
  • {message}
  • : null; + })}
); }; -export default Validation; diff --git a/src/components/LabelledInputField/index.tsx b/src/components/LabelledInputField/index.tsx index 470ea624..496d413c 100644 --- a/src/components/LabelledInputField/index.tsx +++ b/src/components/LabelledInputField/index.tsx @@ -1,4 +1,4 @@ -import { CSSProperties, ChangeEvent, TargetedEvent, forwardRef } from 'preact/compat'; +import { CSSProperties, forwardRef } from 'react'; import { Input, InputProps } from 'react-daisyui'; import { UseFormRegisterReturn } from 'react-hook-form'; import './styles.css'; @@ -10,7 +10,6 @@ interface Props { error?: string; type: string; step?: string; - value: string; extraBtnText?: string; extraBtnAction?: () => void; onChange?: (value: string) => void; @@ -18,7 +17,7 @@ interface Props { register?: UseFormRegisterReturn; } -const LabelledInputField = forwardRef((props: Props & InputProps) => { +export const LabelledInputField = forwardRef((props: Props & InputProps) => { const { register, color, error, label, secondaryLabel, onChange, extraBtnAction, extraBtnText, style, ...rest } = props; @@ -37,16 +36,8 @@ const LabelledInputField = forwardRef((props: Props & InputProps) => { className={`rounded-md border bg-transparent ${!error && 'border-neutral-500'}`} color={inputColor} {...rest} - onFocus={(event: TargetedEvent) => { - if (event.target instanceof HTMLInputElement) { - event.target.select(); - } - }} - onInput={(event: ChangeEvent) => { - if (event.target instanceof HTMLInputElement) { - onChange?.(event.target.value); - } - }} + onFocus={(event) => event.target.select()} + onChange={(event) => onChange?.(event.target.value)} {...register} /> {extraBtnText && extraBtnAction && ( @@ -61,5 +52,3 @@ const LabelledInputField = forwardRef((props: Props & InputProps) => { ); }); - -export default LabelledInputField; diff --git a/src/components/LabelledSelector/index.tsx b/src/components/LabelledSelector/index.tsx index c403406c..9c10564f 100644 --- a/src/components/LabelledSelector/index.tsx +++ b/src/components/LabelledSelector/index.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, CSSProperties } from 'preact/compat'; +import { ChangeEvent, CSSProperties } from 'react'; import { Select } from 'react-daisyui'; import { ofSelect } from '../../helpers/general'; diff --git a/src/components/Layout/ComingSoonTag.tsx b/src/components/Layout/ComingSoonTag.tsx index 9fef727c..50866726 100644 --- a/src/components/Layout/ComingSoonTag.tsx +++ b/src/components/Layout/ComingSoonTag.tsx @@ -1,4 +1,4 @@ -import { FC } from 'preact/compat'; +import { FC } from 'react'; const ComingSoonTag: FC = () => { return
Coming soon!
; diff --git a/src/components/Layout/Nav/NavItem/index.tsx b/src/components/Layout/Nav/NavItem/index.tsx index 873b1a48..b02037a5 100644 --- a/src/components/Layout/Nav/NavItem/index.tsx +++ b/src/components/Layout/Nav/NavItem/index.tsx @@ -1,5 +1,5 @@ import { NavLink } from 'react-router-dom'; -import { LinkItem } from '../../links'; +import { isLottieOptions, LinkItem } from '../../links'; const isExternalLink = (link: string) => { try { @@ -23,6 +23,8 @@ export const NavItem = ({ if (hidden) return null; const isExternal = isExternalLink(link); + if (isLottieOptions(prefix) || isLottieOptions(title)) return <>; + const linkUi = ( <> {prefix} @@ -30,6 +32,7 @@ export const NavItem = ({ {suffix} ); + const cls = `nav-item font-outfit ${props?.className?.() || ''} ${isSubNavItem ? 'text-sm' : ''}`; return isExternal ? ( diff --git a/src/components/Layout/Nav/index.tsx b/src/components/Layout/Nav/index.tsx index cc376f86..b668258e 100644 --- a/src/components/Layout/Nav/index.tsx +++ b/src/components/Layout/Nav/index.tsx @@ -1,8 +1,8 @@ -import { memo, useEffect, useMemo, useState } from 'preact/compat'; +import { memo, useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { useGlobalState } from '../../../GlobalStateProvider'; import { NavCollapseButtonContent } from '../NavCollapseButtonContent'; -import { createLinks, LinkItem } from '../links'; +import { createLinks } from '../links'; import { NavItem } from './NavItem'; import { NavCollapseMenu } from './NavCollapseMenu'; @@ -32,13 +32,13 @@ const Nav = memo(({ onClick }: NavProps) => { return (