diff --git a/.env.kovan-fork b/.env.kovan-fork new file mode 100644 index 0000000..088f9fb --- /dev/null +++ b/.env.kovan-fork @@ -0,0 +1,3 @@ +VITE_CHAIN_ID=42 +VITE_JSON_RPC_PROVIDER_URL=http://127.0.0.1:8545 +VITE_REN_POOL=0xbF115A5538290D234fA31e917241a58A20a847Bc \ No newline at end of file diff --git a/.env.mainnet b/.env.mainnet new file mode 100644 index 0000000..ff5f499 --- /dev/null +++ b/.env.mainnet @@ -0,0 +1,3 @@ +VITE_CHAIN_ID=1 +VITE_JSON_RPC_PROVIDER_URL=https://eth-mainnet.alchemyapi.io/v2/4qQo8naBUQPUU4UWex6jJupYRxlfIdxl +VITE_REN_POOL=0xf1e98770ab8ed1364371B8c7DBdA56633F7cB6a9 diff --git a/.env.mainnet-fork b/.env.mainnet-fork new file mode 100644 index 0000000..fab1289 --- /dev/null +++ b/.env.mainnet-fork @@ -0,0 +1,3 @@ +VITE_CHAIN_ID=1 +VITE_JSON_RPC_PROVIDER_URL=http://localhost:8545 +VITE_REN_POOL=0xf1e98770ab8ed1364371B8c7DBdA56633F7cB6a9 diff --git a/.env.template b/.env.template index 13e664e..4eb23e5 100644 --- a/.env.template +++ b/.env.template @@ -1,2 +1,2 @@ -REACT_APP_CHAIN_ID=42 -REACT_APP_INFURA_PROJECT_ID=XXX +VITE_CHAIN_ID= +INFURA_PROJECT_ID= diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..3716df8 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,24 @@ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + ], + plugins: ["svelte3", "@typescript-eslint"], + ignorePatterns: ["*.cjs"], + overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }], + settings: { + "svelte3/typescript": () => require("typescript"), + }, + parserOptions: { + sourceType: "module", + ecmaVersion: 2019, + }, + env: { + browser: true, + es2017: true, + node: true, + }, +}; diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index fbd3148..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', // Specifies the ESLint parser - parserOptions: { - ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features - sourceType: 'module', // Allows for the use of imports - ecmaFeatures: { - jsx: true, // Allows for the parsing of JSX - }, - }, - settings: { - react: { - version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use - }, - }, - extends: [ - 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react - 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from @typescript-eslint/eslint-plugin - ], - rules: { - // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs - // e.g. '@typescript-eslint/explicit-function-return-type': 'off', - semi: ['error', 'never'], - indent: [2, 2, { SwitchCase: 1 }], - quotes: ['error', 'single'], - }, - overrides: [ - { - 'files': ['**/*.tsx'], - 'rules': { - 'react/prop-types': 'off' - } - } - ] -} diff --git a/.gitignore b/.gitignore index 7077367..0bd0a97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,5 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js +/.svelte-kit +/node_modules/ # testing /coverage @@ -12,13 +8,8 @@ /build # misc -.DS_Store -.env -.env.kovan -.env.dev - -npm-debug.log* -yarn-debug.log* -yarn-error.log* +/.env +/.env.kovan +/.env.dev /.netlify diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..58777d5 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,7 @@ +module.exports = { + arrowParens: "avoid", + tabWidth: 4, + svelteStrictMode: true, + svelteSortOrder: "options-scripts-markup-styles", + plugins: ["prettier-plugin-svelte"], +}; diff --git a/.storybook/main.cjs b/.storybook/main.cjs new file mode 100644 index 0000000..acd52ab --- /dev/null +++ b/.storybook/main.cjs @@ -0,0 +1,37 @@ +const preprocess = require("svelte-preprocess"); + +module.exports = { + stories: [ + "../src/**/*.stories.mdx", + "../src/**/*.stories.@(svelte)", + ], + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-svelte-csf", + { + name: "@storybook/addon-postcss", + options: { + cssLoaderOptions: { + // When you have splitted your css over multiple files + // and use @import('./other-styles.css') + importLoaders: 1, + }, + postcssLoaderOptions: { + // When using postCSS 8 + implementation: require("postcss"), + }, + }, + }, + "@storybook/addon-interactions", + ], + framework: "@storybook/svelte", + svelteOptions: { + // "preprocess": config.preprocess, + preprocess: [ + preprocess({ + postcss: true, + }), + ], + }, +}; diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 0000000..5233636 --- /dev/null +++ b/.storybook/preview.js @@ -0,0 +1,12 @@ +import "../src/app.css"; +import "carbon-components-svelte/css/g100.css"; + +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; diff --git a/README.md b/README.md index 970f9b5..f610030 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,78 @@ # renpool-app + RenPool's client app + +## Manually deploy client app + +[https://www.freecodecamp.org/news/how-to-deploy-a-react-application-to-netlify-363b8a98a985/](https://www.freecodecamp.org/news/how-to-deploy-a-react-application-to-netlify-363b8a98a985/) + +Install Netlify CLI: `npm install netlify-cli -g`. + +```bash +>> yarn run setEnv: +>> yarn run deploy +``` + +The app is deployed to [https://renpool.netlify.app/](https://renpool.netlify.app/) + +## Usage + +1. Open the Brownie console. Starting the console launches a fresh [Ganache](https://www.trufflesuite.com/ganache) instance in the background. + + ```bash + $ brownie console + Brownie v1.9.0 - Python development framework for Ethereum + + ReactMixProject is the active project. + Launching 'ganache-cli'... + Brownie environment is ready. + ``` + +2. Run the [deployment script](scripts/deploy.py) to deploy the project's smart contracts. + + ```python + >>> run("deploy") + Running 'scripts.deploy.main'... + Transaction sent: 0xd1000d04fe99a07db864bcd1095ddf5cb279b43be8e159f94dbff9d4e4809c70 + Gas price: 0.0 gwei Gas limit: 6721975 + SolidityStorage.constructor confirmed - Block: 1 Gas used: 110641 (1.65%) + SolidityStorage deployed at: 0xF104A50668c3b1026E8f9B0d9D404faF8E42e642 + + Transaction sent: 0xee112392522ed24ac6ab8cc8ba09bfe51c5d699d9d1b39294ba87e5d2a56212c + Gas price: 0.0 gwei Gas limit: 6721975 + VyperStorage.constructor confirmed - Block: 2 Gas used: 134750 (2.00%) + VyperStorage deployed at: 0xB8485421abC325D172652123dBd71D58b8117070 + ``` + +3. While Brownie is still running, start the React app in a different terminal. + + ```bash + # make sure to use a different terminal, not the brownie console + cd client + yarn start + ``` + +4. Connect Metamask to the local Ganache network. In the upper right corner, click the network dropdown menu. Select `Localhost 8545`, or: + + ```bash + New Custom RPC + http://localhost:8545 + ``` + +5. Interact with the smart contracts using the web interface or via the Brownie console. + + ```python + # get the newest vyper storage contract + >>> vyper_storage = VyperStorage[-1] + + # the default sender of the transaction is the contract creator + >>> vyper_storage.set(1337) + ``` + + Any changes to the contracts from the console should show on the website after a refresh, and vice versa. + +## Ending a Session + +When you close the Brownie console, the Ganache instance also terminates and the deployment artifacts are deleted. + +To retain your deployment artifacts (and their functionality) you can launch Ganache yourself prior to launching Brownie. Brownie automatically attaches to the ganache instance where you can deploy the contracts. After closing Brownie, the chain and deployment artifacts will persist. diff --git a/_redirects b/_redirects deleted file mode 100644 index 3ce9216..0000000 --- a/_redirects +++ /dev/null @@ -1 +0,0 @@ -/* /index.html 200 diff --git a/jest.config.js b/jest.config.js index 3e92b2c..56c978a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,23 +1,16 @@ -module.exports = { - // The root of your source code, typically /src - // `` is a token Jest substitutes - roots: ['/src'], - // Jest transformations -- this adds support for TypeScript - // using ts-jest - transform: { - '^.+\\.tsx?$': 'ts-jest' - }, - // Runs special logic, such as cleaning up components - // when using React Testing Library and adds special - // extended assertions to Jest - setupFilesAfterEnv: [ - '@testing-library/react/cleanup-after-each', - '@testing-library/jest-dom/extend-expect' - ], - // Test spec file resolution pattern - // Look for tests inside the `src` folder and filename - // should contain `test` or `spec`. - testRegex: '(/**/.*|(\\.|/)(test|spec))\\.tsx?$', - // Module file extensions for importing - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], -} +/** @type {import('ts-jest').InitialOptionsTsJest} */ +const config = { + transform: { + "^.+\\.ts$": "ts-jest", + "^.+\\.svelte$": [ + "svelte-jester", + { + preprocess: true, + }, + ], + }, + moduleFileExtensions: ["js", "ts", "svelte"], + setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"], +}; + +export default config; diff --git a/package.json b/package.json index 12d070d..7cd9f2d 100644 --- a/package.json +++ b/package.json @@ -1,82 +1,70 @@ { - "name": "client", - "version": "0.1.0", - "private": true, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "test:watch": "yarn run test --watch", - "eject": "react-scripts eject", - "lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix", - "setEnv:dev": "cp .env.dev .env", - "setEnv:kovan": "cp .env.kovan .env", - "setEnv:rinkeby": "cp .env.rinkeby .env", - "deploy": "yarn run build && cp ./_redirects ./build && netlify deploy --dir build --prod" - }, - "dependencies": { - "renpool-contracts": "https://github.com/renpool/renpool-contracts", - "@ethersproject/address": "^5.4.0", - "@ethersproject/bignumber": "^5.4.0", - "@ethersproject/contracts": "^5.4.0", - "@ethersproject/providers": "^5.4.1", - "@ethersproject/strings": "^5.4.0", - "@ethersproject/units": "^5.4.0", - "@renproject/react-components": "^1.0.0-alpha.10", - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.1.0", - "@testing-library/user-event": "^12.1.10", - "@types/bs58": "^4.0.1", - "@types/jest": "^26.0.15", - "@types/lodash": "^4.14.170", - "@types/node": "^12.0.0", - "@types/react": "^17.0.0", - "@types/react-dom": "^17.0.0", - "@types/styled-components": "^5.1.11", - "@web3-react/core": "^6.1.9", - "@web3-react/injected-connector": "^6.0.7", - "@web3-react/network-connector": "^6.1.9", - "bs58": "^4.0.1", - "lodash": "^4.17.21", - "react": "^17.0.2", - "react-device-detect": "^1.17.0", - "react-dom": "^17.0.2", - "react-scripts": "4.0.3", - "rimble-ui": "^0.14.0", - "styled-components": "^5.3.0", - "typescript": "^4.1.2", - "web-vitals": "^1.0.1" - }, - "devDependencies": { - "@typescript-eslint/eslint-plugin": "^4.27.0", - "@typescript-eslint/parser": "^4.27.0" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "jest": { - "testMatch": [ - "**/?(*.)(spec|test).ts?(x)" - ], - "coveragePathIgnorePatterns": [ - "/node_modules/", - "/Tests/Setup.js", - "/Tests/Mocks/*" - ] - } + "name": "client", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "yarn dev:kovan", + "dev:kovan": "MODE=kovan svelte-kit dev", + "dev:kovan-fork": "MODE=kovan-fork svelte-kit dev", + "dev:mainnet": "MODE=mainnet svelte-kit dev", + "dev:mainnet-fork": "MODE=mainnet-fork svelte-kit dev", + "build": "svelte-kit build", + "package": "svelte-kit package", + "preview": "svelte-kit preview", + "check": "svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --ignore-path .gitignore --write . --ext .cjs,.js,.ts,.svelte,.json,.html", + "lint": "eslint --ignore-path .gitignore . --ext .cjs,.js,.ts,.svelte,.json,.html", + "lint:fix": "eslint --ignore-path .gitignore --fix . --ext .cjs,.js,.ts,.svelte,.json,.html", + "prelint": "yarn run format", + "test": "jest src", + "test:watch": "yarn test --watch", + "storybook": "start-storybook -p 6006", + "build-storybook": "build-storybook" + }, + "devDependencies": { + "@metamask/providers": "^8.1.1", + "@storybook/addon-actions": "^6.4.7", + "@storybook/addon-essentials": "^6.4.7", + "@storybook/addon-interactions": "^6.4.8", + "@storybook/addon-links": "^6.4.7", + "@storybook/addon-postcss": "^2.0.0", + "@storybook/addon-svelte-csf": "^1.1.0", + "@storybook/svelte": "^6.4.7", + "@storybook/testing-library": "^0.0.7", + "@sveltejs/adapter-auto": "next", + "@sveltejs/kit": "next", + "@testing-library/jest-dom": "^5.16.1", + "@testing-library/svelte": "^3.0.3", + "@types/jest": "^27.0.3", + "@typescript-eslint/eslint-plugin": "^4.31.1", + "@typescript-eslint/parser": "^4.31.1", + "autoprefixer": "^10.4.0", + "carbon-components-svelte": "^0.50.2", + "carbon-preprocess-svelte": "^0.6.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-svelte3": "^3.2.1", + "jest": "^27.4.3", + "postcss": "^8.4.4", + "prettier": "^2.5.1", + "prettier-plugin-svelte": "^2.5.1", + "svelte": "^3.44.2", + "svelte-check": "^2.2.6", + "svelte-jester": "^2.1.5", + "svelte-loader": "^3.1.2", + "svelte-preprocess": "^4.9.4", + "tailwindcss": "^2.2.19", + "ts-jest": "^27.1.0", + "tslib": "^2.3.1", + "typescript": "^4.4.3" + }, + "type": "module", + "dependencies": { + "ethers": "^5.5.1", + "renpool-contracts": "https://github.com/renpool/renpool-contracts" + }, + "resolutions": { + "acorn": "8.0.1" + } } diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..67cdf1a --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777c..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 399cdc8..0000000 --- a/public/index.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - RenPool - - - -
- - - diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index a4a7e45..0000000 --- a/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "RenPool", - "name": "RenPool", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index 330ea2b..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React, { useContext, useState } from 'react' -import { Flex, Box } from 'rimble-ui' -import { RenPoolContext } from './context/RenPoolProvider' -import { useActiveWeb3React } from './hooks/useActiveWeb3React' -import { DepositScreen } from './screens/DepositScreen' -import { WithdrawScreen } from './screens/WithdrawScreen' -import { AdminScreen } from './screens/AdminScreen' -import { StatsSection } from './sections/StatsSection' -import { AddressesSection } from './sections/AddressesSection' -import { Header } from './components/Header' -import { Banners } from './components/Banners' -import { NavLink } from './components/NavLink' -// import { Instructions } from './components/Instructions' -import { Footer } from './components/Footer' - -enum Views { - DEPOSIT = 'DEPOSIT', - WITHDRAW = 'WITHDRAW', - ADMIN = 'ADMIN', -} - -export const App = (): JSX.Element => { - const { account } = useActiveWeb3React() - const { owner, nodeOperator } = useContext(RenPoolContext) - const [view, setView] = useState(Views.DEPOSIT) - - // TODO: display darknodeUrl once registered - - return ( - <> -
- -
- - - - - - { setView(Views.DEPOSIT) }} - /> - | - { setView(Views.WITHDRAW) }} - /> - {account != null && [owner, nodeOperator].includes(account) && ( - <> - | - { setView(Views.ADMIN) }} - /> - - )} - - - - - {view === Views.DEPOSIT && ( - - )} - - {view === Views.WITHDRAW && ( - - )} - - {view === Views.ADMIN && ( - - )} - - {[Views.DEPOSIT, Views.WITHDRAW].includes(view) && ( - <> - - - - - - {/* */} - - )} - -
-
- - ) -} diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..70984a7 --- /dev/null +++ b/src/app.css @@ -0,0 +1,15 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + h1 { + @apply sm:text-3xl text-2xl font-medium mb-2 text-gray-900; + } + h2 { + @apply sm:text-2xl text-xl font-medium mb-2 text-gray-900; + } + .underline-title { + @apply h-0.5 w-20 bg-yellow-500 rounded mb-4; + } +} diff --git a/src/app.html b/src/app.html new file mode 100644 index 0000000..fb6f092 --- /dev/null +++ b/src/app.html @@ -0,0 +1,21 @@ + + + + + + + + + + + %svelte.head% + + + +
%svelte.body%
+ + diff --git a/src/components/AmountForm/index.tsx b/src/components/AmountForm/index.tsx index cc79029..601221a 100644 --- a/src/components/AmountForm/index.tsx +++ b/src/components/AmountForm/index.tsx @@ -1,88 +1,97 @@ -import React, { FC, useState, ChangeEvent, FormEvent } from 'react' -import { BigNumber } from '@ethersproject/bignumber' -import { formatUnits } from '@ethersproject/units' -import { Box, Form, Input, Button } from 'rimble-ui' -import { ButtonVariants } from '../../types/button' -import { DECIMALS } from '../../constants' -import { str2BN } from '../../utils/str2BN' +import React, { FC, useState, ChangeEvent, FormEvent } from "react"; +import { BigNumber } from "@ethersproject/bignumber"; +import { formatUnits } from "@ethersproject/units"; +import { Box, Form, Input, Button } from "rimble-ui"; +import { ButtonVariants } from "../../types/button"; +import { DECIMALS } from "../../constants"; +import { str2BN } from "../../utils/str2BN"; export interface Props { - btnLabel: string - btnVariant?: keyof typeof ButtonVariants - disabled: boolean - upperBound: BigNumber - onBefore?: () => void - onClientCancel?: () => void - onClientError?: (err?: string) => void - onSuccess?: (amount: BigNumber) => void + btnLabel: string; + btnVariant?: keyof typeof ButtonVariants; + disabled: boolean; + upperBound: BigNumber; + onBefore?: () => void; + onClientCancel?: () => void; + onClientError?: (err?: string) => void; + onSuccess?: (amount: BigNumber) => void; } export const AmountForm: FC = ({ - btnLabel = 'Submit', - btnVariant = ButtonVariants.default, - disabled, - upperBound, - onBefore = () => null, - onClientCancel = () => null, - onClientError = () => null, - onSuccess = () => null, + btnLabel = "Submit", + btnVariant = ButtonVariants.default, + disabled, + upperBound, + onBefore = () => null, + onClientCancel = () => null, + onClientError = () => null, + onSuccess = () => null, }): JSX.Element => { - const [amount, setAmount] = useState('0') + const [amount, setAmount] = useState("0"); - const handleChange = async (e: ChangeEvent): Promise => { - const str: string = e.target.value - // Enforce numbers only - const regex = /[0-9]/g - const _amount = str.match(regex)?.join('') || '' - setAmount(_amount) - } + const handleChange = async ( + e: ChangeEvent + ): Promise => { + const str: string = e.target.value; + // Enforce numbers only + const regex = /[0-9]/g; + const _amount = str.match(regex)?.join("") || ""; + setAmount(_amount); + }; - const handleSubmit = async (e: FormEvent): Promise => { - e.preventDefault() + const handleSubmit = async ( + e: FormEvent + ): Promise => { + e.preventDefault(); - // Run 'before' logic if provided and return on error - try { - onBefore() - } catch (e) { - onClientCancel() - return // return silently - } + // Run 'before' logic if provided and return on error + try { + onBefore(); + } catch (e) { + onClientCancel(); + return; // return silently + } - const _amount = str2BN(amount) + const _amount = str2BN(amount); - // Validate input. In case of errors, display on UI and return handler to parent component - if (_amount.lt(BigNumber.from(1))) { - onClientError('Please, enter a valid amount.') - return - } + // Validate input. In case of errors, display on UI and return handler to parent component + if (_amount.lt(BigNumber.from(1))) { + onClientError("Please, enter a valid amount."); + return; + } - if (_amount.gt(upperBound)) { - onClientError(`Insufficient balance (${parseInt(formatUnits(upperBound.toString(), DECIMALS), 10)} REN).`) - return - } + if (_amount.gt(upperBound)) { + onClientError( + `Insufficient balance (${parseInt( + formatUnits(upperBound.toString(), DECIMALS), + 10 + )} REN).` + ); + return; + } - // Pass event up to parent component - onSuccess(_amount) - } + // Pass event up to parent component + onSuccess(_amount); + }; - return ( -
- - - - - ) -} + return ( +
+ + + + + ); +}; diff --git a/src/components/Banners/index.tsx b/src/components/Banners/index.tsx index fd530fa..8168c54 100644 --- a/src/components/Banners/index.tsx +++ b/src/components/Banners/index.tsx @@ -1,40 +1,43 @@ -import React, { useContext } from 'react' -import { Flash, Box, Text } from 'rimble-ui' -import { NETWORKS } from '../../constants' -import { RenPoolContext } from '../../context/RenPoolProvider' -import { useConnection } from '../../hooks/useConnection' +import React, { useContext } from "react"; +import { Flash, Box, Text } from "rimble-ui"; +import { NETWORKS } from "../../constants"; +import { RenPoolContext } from "../../context/RenPoolProvider"; +import { useConnection } from "../../hooks/useConnection"; -const CHAIN_ID = process.env.REACT_APP_CHAIN_ID +const CHAIN_ID = process.env.REACT_APP_CHAIN_ID; export const Banners = (): JSX.Element => { - const { isAccountLocked, isWrongChain } = useConnection() - const { isLocked } = useContext(RenPoolContext) + const { isAccountLocked, isWrongChain } = useConnection(); + const { isLocked } = useContext(RenPoolContext); - return ( - <> - {isAccountLocked && ( - - - Please, connect with MetaMask - - - )} + return ( + <> + {isAccountLocked && ( + + + Please, connect with MetaMask + + + )} - {isWrongChain && ( - - - Please, switch network to {NETWORKS[CHAIN_ID]} - - - )} + {isWrongChain && ( + + + Please, switch network to{" "} + + {NETWORKS[CHAIN_ID]} + + + + )} - {isLocked && ( - - - The pool is locked - - - )} - - ) -} + {isLocked && ( + + + The pool is locked + + + )} + + ); +}; diff --git a/src/components/DarknodeUrlForm/index.tsx b/src/components/DarknodeUrlForm/index.tsx index 2c3db23..9ff8877 100644 --- a/src/components/DarknodeUrlForm/index.tsx +++ b/src/components/DarknodeUrlForm/index.tsx @@ -1,128 +1,134 @@ -import React, { FC, useState, ChangeEvent, FormEvent } from 'react' -import { Box, Form, Button } from 'rimble-ui' -import { ButtonVariants } from '../../types/button' +import React, { FC, useState, ChangeEvent, FormEvent } from "react"; +import { Box, Form, Button } from "rimble-ui"; +import { ButtonVariants } from "../../types/button"; -const CHAIN_ID = process.env.REACT_APP_CHAIN_ID +const CHAIN_ID = process.env.REACT_APP_CHAIN_ID; -const DARKNODE_NETWORK = CHAIN_ID === '1' ? 'mainnet' : 'testnet' -const DARKNODE_BASE_URL = `https://${DARKNODE_NETWORK}.renproject.io/darknode/` -const DARKNODE_URL_PLACEHOLDER = `https://${DARKNODE_NETWORK}.renproject.io/darknode/?action=register&public_key=&name=` +const DARKNODE_NETWORK = CHAIN_ID === "1" ? "mainnet" : "testnet"; +const DARKNODE_BASE_URL = `https://${DARKNODE_NETWORK}.renproject.io/darknode/`; +const DARKNODE_URL_PLACEHOLDER = `https://${DARKNODE_NETWORK}.renproject.io/darknode/?action=register&public_key=&name=`; enum ErrorMessages { - EMPTY_STRING = 'EMPTY_STRING', - START_WITH = 'START_WITH', - ACTION = 'ACTION', - PUBLIC_KEY = 'PUBLIC_KEY', - NAME = 'NAME', + EMPTY_STRING = "EMPTY_STRING", + START_WITH = "START_WITH", + ACTION = "ACTION", + PUBLIC_KEY = "PUBLIC_KEY", + NAME = "NAME", } export interface Props { - btnLabel: string - btnVariant?: keyof typeof ButtonVariants - disabled: boolean - onBefore?: () => void - onClientCancel?: () => void - onClientError?: (err?: string) => void - onSuccess?: ({ darknodeID, publicKey }: DarknodeParams) => void + btnLabel: string; + btnVariant?: keyof typeof ButtonVariants; + disabled: boolean; + onBefore?: () => void; + onClientCancel?: () => void; + onClientError?: (err?: string) => void; + onSuccess?: ({ darknodeID, publicKey }: DarknodeParams) => void; } export interface DarknodeParams { - darknodeID: string - publicKey: string + darknodeID: string; + publicKey: string; } export const DarknoneUrlForm: FC = ({ - btnLabel, - btnVariant = ButtonVariants.default, - disabled, - onBefore = () => null, - onClientCancel = () => null, - onClientError = () => null, - onSuccess = () => null, + btnLabel, + btnVariant = ButtonVariants.default, + disabled, + onBefore = () => null, + onClientCancel = () => null, + onClientError = () => null, + onSuccess = () => null, }): JSX.Element => { - const [darknodeUrl, setDarknodeUrl] = useState('') - - const validateDarknodeUrl = (url: string): { isValid: boolean, err: string | null } => { - if (url.trim().length === 0) { - return { isValid: false, err: ErrorMessages.EMPTY_STRING } - } - if (!url.startsWith(DARKNODE_BASE_URL)) { - return { isValid: false, err: ErrorMessages.START_WITH } - } - if (!url.includes('action=register')) { - return { isValid: false, err: ErrorMessages.ACTION } - } - if (!url.includes('&public_key=0x')) { - return { isValid: false, err: ErrorMessages.PUBLIC_KEY } - } - if (!url.includes('&name=')) { - return { isValid: false, err: ErrorMessages.NAME } - } - return { isValid: true, err: null } - } - - const getDarknodeUrlParams = (url: string): DarknodeParams => { - const darknodeID = url.slice(DARKNODE_BASE_URL.length).split('?')[0] // base58 - - const p1 = 'public_key=' - const p2 = '&name=' - const index1 = url.indexOf(p1) - const index2 = url.indexOf(p2) - const publicKey = url.slice(index1 + p1.length, index2) - - return { darknodeID, publicKey } - } - - const handleChange = async (e: ChangeEvent): Promise => { - setDarknodeUrl(e.target.value || '') - } - - const handleSubmit = async (e: FormEvent): Promise => { - e.preventDefault() - - // Run 'before' logic if provided and return on error - try { - onBefore() - } catch (e) { - onClientCancel() - return // return silently - } - - // Validate input - const { isValid, err } = validateDarknodeUrl(darknodeUrl) - - // In case of errors, display on UI and return handler to parent component - if (!isValid) { - onClientError(`Invalid url. Reason: ${err}`) - return - } - - // Get darknode params - const { darknodeID, publicKey } = getDarknodeUrlParams(darknodeUrl) - - // Pass event up to parent component - onSuccess({ darknodeID, publicKey }) - } - - return ( -
-