diff --git a/.env b/.env deleted file mode 100644 index 0c48d3cda..000000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -PORT=3000 -CHOKIDAR_USEPOLLING=true diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..c0f017649 --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +PORT=3000 +CHOKIDAR_USEPOLLING=true +REACT_APP_WALLET_CONNECT_PROJECT_ID="" +REACT_APP_ALCHEMY_ID="" + +#DEFAULT +REACT_APP_DEFAULT_CHAIN_ID= +REACT_APP_DEFAULT_BASE_URL= + +#MATIC_MAIN +REACT_APP_MAIN_MATIC_BASE_URL= + +#MATIC_TEST +REACT_APP_TEST_MATIC_BASE_URL= + +#ETHERIUM +REACT_APP_MAIN_ETHERIUM_BASE_URL= + diff --git a/.eslintrc.js b/.eslintrc.js index e08f0b57a..d1b8f8f17 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,4 +23,4 @@ module.exports = { // 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", }, -} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e080cd5fb..8d595a915 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ yarn.lock .env.development.local .env.test.local .env.production.local +.env npm-debug.log* yarn-debug.log* diff --git a/README.md b/README.md index 1185d8047..47f911a93 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,21 @@ -[![@coreui coreui](https://img.shields.io/badge/@coreui%20-coreui-lightgrey.svg?style=flat-square)](https://github.com/coreui/coreui) -[![npm package][npm-coreui-badge]][npm-coreui] -[![NPM downloads][npm-coreui-download]][npm-coreui] -[![@coreui react](https://img.shields.io/badge/@coreui%20-react-lightgrey.svg?style=flat-square)](https://github.com/coreui/react) -[![npm package][npm-coreui-react-badge]][npm-coreui-react] -[![NPM downloads][npm-coreui-react-download]][npm-coreui-react] - -[npm-coreui]: https://www.npmjs.com/package/@coreui/coreui -[npm-coreui-badge]: https://img.shields.io/npm/v/@coreui/coreui.png?style=flat-square -[npm-coreui-download]: https://img.shields.io/npm/dm/@coreui/coreui.svg?style=flat-square -[npm-coreui-react]: https://www.npmjs.com/package/@coreui/react -[npm-coreui-react-badge]: https://img.shields.io/npm/v/@coreui/react.png?style=flat-square -[npm-coreui-react-download]: https://img.shields.io/npm/dm/@coreui/react.svg?style=flat-square -[npm]: https://www.npmjs.com/package/@coreui/react - -# CoreUI Free React Admin Template - -CoreUI is meant to be the UX game changer. Pure & transparent code is devoid of redundant components, so the app is light enough to offer ultimate user experience. This means mobile devices also, where the navigation is just as easy and intuitive as on a desktop or laptop. The CoreUI Layout API lets you customize your project for almost any device – be it Mobile, Web or WebApp – CoreUI covers them all! - -## Table of Contents - -* [Versions](#versions) -* [CoreUI Pro](#coreui-pro) -* [Quick Start](#quick-start) -* [Installation](#installation) -* [Basic usage](#basic-usage) -* [What's included](#whats-included) -* [Documentation](#documentation) -* [Versioning](#versioning) -* [Creators](#creators) -* [Community](#community) -* [Support CoreUI Development](#support-coreui-development) -* [Copyright and License](#copyright-and-license) - -## Versions - -* [CoreUI Free Bootstrap Admin Template](https://github.com/coreui/coreui-free-bootstrap-admin-template) -* [CoreUI Free Angular Admin Template](https://github.com/coreui/coreui-free-angular-admin-template) -* [CoreUI Free React.js Admin Template](https://github.com/coreui/coreui-free-react-admin-template) -* [CoreUI Free Vue.js Admin Template](https://github.com/coreui/coreui-free-vue-admin-template) - -## CoreUI Pro - -* πŸ’ͺ [CoreUI Pro Angular Admin Template](https://coreui.io/product/angular-dashboard-template/) -* πŸ’ͺ [CoreUI Pro Bootstrap Admin Template](https://coreui.io/product/bootstrap-dashboard-template/) -* πŸ’ͺ [CoreUI Pro React Admin Template](https://coreui.io/product/react-dashboard-template/) -* πŸ’ͺ [CoreUI Pro Vue Admin Template](https://coreui.io/product/vue-dashboard-template/) +# CoreUI Web3 React Admin Template +This project is a web3 version of the CoreUI React admin template. It is a fork of the CoreUI admin template built with React. + +Overview +We have extended the functionality of the original CoreUI template by adding web3 capabilities using the following libraries: + +- 🌈RainBowKit - RainbowKit is a React library that makes it easy to add wallet connection to your dapp. It's intuitive, responsive and customizable. +- Axios - Axios is a simple promise based HTTP client for the browser and node.js. Axios provides a simple to use library in a small package with a very extensible interface. +- Redux - For state management. +- Redux Saga - For managing side effects, such as fetching blockchain data. +- Multichain config - configs are set by chain. + +The CoreUI template already utilizes React and Bootstrap to provide a robust admin template with a wide range of UI components. ## Quick Start +To get started with the project: -- [Download the latest release](https://github.com/coreui/coreui-free-react-admin-template/archive/refs/heads/main.zip) -- Clone the repo: `git clone https://github.com/coreui/coreui-free-react-admin-template.git` +Clone the repository: git clone https://github.com/treejer/coreui-free-react-admin-template-web3 ### Installation @@ -95,101 +61,22 @@ or $ yarn build ``` -## What's included - -Within the download you'll find the following directories and files, logically grouping common assets and providing both compiled and minified variations. You'll see something like this: - -``` -coreui-free-react-admin-template -β”œβ”€β”€ public/ # static files -β”‚ └── index.html # html template -β”‚ -β”œβ”€β”€ src/ # project root -β”‚ β”œβ”€β”€ assets/ # images, icons, etc. -β”‚ β”œβ”€β”€ components/ # common components - header, footer, sidebar, etc. -β”‚ β”œβ”€β”€ layouts/ # layout containers -β”‚ β”œβ”€β”€ scss/ # scss styles -β”‚ β”œβ”€β”€ views/ # application views -β”‚ β”œβ”€β”€ _nav.js # sidebar navigation config -β”‚ β”œβ”€β”€ App.js -β”‚ β”œβ”€β”€ ... -β”‚ β”œβ”€β”€ index.js -β”‚ β”œβ”€β”€ routes.js # routes config -β”‚ └── store.js # template state example -β”‚ -└── package.json +## Env Setup +Duplicate the environment file and modify its variable values. +```bash +cp .env.example .env ``` ## Documentation The documentation for the CoreUI Admin Template is hosted at our website [CoreUI for React](https://coreui.io/react/) -## Versioning - -For transparency into our release cycle and in striving to maintain backward compatibility, CoreUI Free Admin Template is maintained under [the Semantic Versioning guidelines](http://semver.org/). - -See [the Releases section of our project](https://github.com/coreui/coreui-free-react-admin-template/releases) for changelogs for each release version. - -## Creators - -**Łukasz Holeczek** -* -* -* - -**CoreUI team** -* https://github.com/orgs/coreui/people - -## Community - -Get updates on CoreUI's development and chat with the project maintainers and community members. - -- Follow [@core_ui on Twitter](https://twitter.com/core_ui). -- Read and subscribe to [CoreUI Blog](https://coreui.ui/blog/). - -## Support CoreUI Development - -CoreUI is an MIT-licensed open source project and is completely free to use. However, the amount of effort needed to maintain and develop new features for the project is not sustainable without proper financial backing. You can support development by buying the [CoreUI PRO](https://coreui.io/pricing/) or by becoming a sponsor via [Open Collective](https://opencollective.com/coreui/). - - -### Platinum Sponsors +### About -Support this project by [becoming a Platinum Sponsor](https://opencollective.com/coreui/contribute/platinum-sponsor-40959/). A large company logo will be added here with a link to your website. +This project is built and maintained by the [Treejer Team](https://github.com/treejer). The source code is available on [GitHub]([https://github.com/treejer](https://github.com/treejer/coreui-free-react-admin-template-web3)). +Please feel free to reach out if you have any questions or need further assistance. We are happy to help! - - -### Gold Sponsors - -Support this project by [becoming a Gold Sponsor](https://opencollective.com/coreui/contribute/gold-sponsor-40960/). A big company logo will be added here with a link to your website. - - - -### Silver Sponsors - -Support this project by [becoming a Silver Sponsor](https://opencollective.com/coreui/contribute/silver-sponsor-40967/). A medium company logo will be added here with a link to your website. - - - -### Bronze Sponsors - -Support this project by [becoming a Bronze Sponsor](https://opencollective.com/coreui/contribute/bronze-sponsor-40966/). The company avatar will show up here with a link to your OpenCollective Profile. - - - -### Backers - -Thanks to all the backers and sponsors! Support this project by [becoming a backer](https://opencollective.com/coreui/contribute/backer-40965/). - - - - - -## Copyright and License - -copyright 2023 creativeLabs Łukasz Holeczek. +## Documentation - Code released under [the MIT license](https://github.com/coreui/coreui-free-react-admin-template/blob/master/LICENSE). -There is only one limitation you can't can’t re-distribute the CoreUI as stock. You can’t do this if you modify the CoreUI. In past we faced some problems with persons who tried to sell CoreUI based templates. - diff --git a/package.json b/package.json index da08efaab..02941661d 100644 --- a/package.json +++ b/package.json @@ -22,38 +22,45 @@ "test:debug": "react-scripts --inspect-brk test --runInBand" }, "dependencies": { - "@coreui/chartjs": "^3.1.1", - "@coreui/coreui": "^4.2.6", - "@coreui/icons": "^3.0.1", - "@coreui/icons-react": "^2.1.0", - "@coreui/react": "^4.6.0", - "@coreui/react-chartjs": "^2.1.2", - "@coreui/utils": "^2.0.1", - "chart.js": "^3.9.1", - "classnames": "^2.3.2", - "core-js": "^3.29.0", - "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-app-polyfill": "^3.0.0", - "react-dom": "^18.2.0", - "react-redux": "^8.0.5", - "react-router-dom": "^6.8.2", + "@coreui/chartjs": "3.1.2", + "@coreui/coreui": "4.2.6", + "@coreui/icons": "3.0.1", + "@coreui/icons-react": "2.1.0", + "@coreui/react": "4.9.0-rc.0", + "@coreui/react-chartjs": "2.1.3", + "@coreui/utils": "2.0.2", + "@rainbow-me/rainbowkit": "1.0.7", + "@reduxjs/toolkit": "1.9.5", + "axios": "1.4.0", + "chart.js": "3.9.1", + "classnames": "2.3.2", + "core-js": "3.31.0", + "prop-types": "15.8.1", + "react": "18.2.0", + "react-app-polyfill": "3.0.0", + "react-dom": "18.2.0", + "react-redux": "8.1.1", + "react-router-dom": "6.14.0", "redux": "4.2.1", - "simplebar-react": "^2.4.3" + "redux-fetch-state": "0.2.4", + "redux-saga": "1.2.3", + "simplebar-react": "2.4.3", + "viem": "1.4.2", + "wagmi": "1.3.9" }, "devDependencies": { - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.4.3", - "eslint-config-prettier": "^8.7.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "2.8.4", + "@testing-library/jest-dom": "5.16.5", + "@testing-library/react": "14.0.0", + "@testing-library/user-event": "14.4.3", + "eslint-config-prettier": "8.8.0", + "eslint-plugin-prettier": "4.2.1", + "prettier": "2.8.8", "react-scripts": "5.0.1", - "sass": "^1.58.3", - "web-vitals": "^3.1.1" + "sass": "1.63.6", + "web-vitals": "3.3.2" }, "engines": { "node": ">=10", "npm": ">=6" } -} +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index b25132580..21d293335 100644 --- a/public/index.html +++ b/public/index.html @@ -2,9 +2,9 @@ diff --git a/src/components/AppHeader.js b/src/components/AppHeader.js index dd5f544e3..58ebba445 100644 --- a/src/components/AppHeader.js +++ b/src/components/AppHeader.js @@ -1,6 +1,5 @@ -import React from 'react' +import React, { useEffect } from 'react' import { NavLink } from 'react-router-dom' -import { useSelector, useDispatch } from 'react-redux' import { CContainer, CHeader, @@ -13,22 +12,24 @@ import { } from '@coreui/react' import CIcon from '@coreui/icons-react' import { cilBell, cilEnvelopeOpen, cilList, cilMenu } from '@coreui/icons' - +import { useToggleSidebar, useInit } from '../redux/modules/init/slice' import { AppBreadcrumb } from './index' import { AppHeaderDropdown } from './header/index' import { logo } from 'src/assets/brand/logo' +import { WalletButton } from './rainbow/index' const AppHeader = () => { - const dispatch = useDispatch() - const sidebarShow = useSelector((state) => state.sidebarShow) + const { toggleSidebar } = useToggleSidebar() + const { dispatchInit } = useInit() + + useEffect(() => { + dispatchInit() + }, []) return ( - dispatch({ type: 'set', sidebarShow: !sidebarShow })} - > + @@ -47,6 +48,9 @@ const AppHeader = () => { Settings + + + diff --git a/src/components/AppSidebar.js b/src/components/AppSidebar.js index a75bf6523..7d9a713d5 100644 --- a/src/components/AppSidebar.js +++ b/src/components/AppSidebar.js @@ -1,34 +1,21 @@ import React from 'react' -import { useSelector, useDispatch } from 'react-redux' - +import { useSelector } from 'react-redux' import { CSidebar, CSidebarBrand, CSidebarNav, CSidebarToggler } from '@coreui/react' import CIcon from '@coreui/icons-react' - import { AppSidebarNav } from './AppSidebarNav' - import { logoNegative } from 'src/assets/brand/logo-negative' import { sygnet } from 'src/assets/brand/sygnet' - import SimpleBar from 'simplebar-react' import 'simplebar/dist/simplebar.min.css' - -// sidebar nav config import navigation from '../_nav' +import { useToggleSidebar } from '../redux/modules/init/slice' const AppSidebar = () => { - const dispatch = useDispatch() const unfoldable = useSelector((state) => state.sidebarUnfoldable) - const sidebarShow = useSelector((state) => state.sidebarShow) + const { sidebarShow, toggleSidebar } = useToggleSidebar() return ( - { - dispatch({ type: 'set', sidebarShow: visible }) - }} - > + @@ -38,10 +25,7 @@ const AppSidebar = () => { - dispatch({ type: 'set', sidebarUnfoldable: !unfoldable })} - /> + ) } diff --git a/src/components/rainbow/button.js b/src/components/rainbow/button.js new file mode 100644 index 000000000..9a4c591f9 --- /dev/null +++ b/src/components/rainbow/button.js @@ -0,0 +1,54 @@ +import React, { useEffect } from 'react' +import { CButton } from '@coreui/react' +import { RainbowKitProvider, ConnectButton } from '@rainbow-me/rainbowkit' +import { configureChains, useAccount, useNetwork } from 'wagmi' +import { mainnet, polygon, polygonMumbai } from 'wagmi/chains' +import { alchemyProvider } from 'wagmi/providers/alchemy' +import { useGetNonce } from '../../redux/modules/userNonce' +import { useUserSign } from '../../redux/modules/userSign' +import { publicProvider } from 'wagmi/providers/public' +import '@rainbow-me/rainbowkit/styles.css' + +const apiKey = process.env.REACT_APP_ALCHEMY_ID +const supportedChains = [mainnet, polygon, polygonMumbai] +const providers = [alchemyProvider({ apiKey }), publicProvider()] +const { chains } = configureChains(supportedChains, providers) + +const RainbowButton = () => { + const { address } = useAccount() + const { chain } = useNetwork() + const { dispatchGetNonce, loading: userNonceLoading } = useGetNonce() + const { dispatchRemoveToken, userSign, loading: userSignLoading } = useUserSign() + const userToken = userSign?.access_token + const isLoading = userNonceLoading || userSignLoading + + useEffect(() => { + if (chain) { + dispatchRemoveToken() + } + }, [chain, dispatchRemoveToken]) + + const handleSignInWallet = async () => { + try { + await dispatchGetNonce(address) + } catch (error) { + console.error('Error signing in:', error) + } + } + + const showSignInWalletButton = !userToken && address && !chain.unsupported + return ( + <> + {showSignInWalletButton && ( + + {isLoading ? 'Signing In...' : 'Sign In Wallet'} + + )} + + + + + ) +} + +export default RainbowButton diff --git a/src/components/rainbow/index.js b/src/components/rainbow/index.js new file mode 100644 index 000000000..1784d6c4c --- /dev/null +++ b/src/components/rainbow/index.js @@ -0,0 +1,34 @@ +// src/components/rainbow/index.js + +import React from 'react' +import { getDefaultWallets } from '@rainbow-me/rainbowkit' +import { configureChains, createConfig, WagmiConfig } from 'wagmi' +import { mainnet, polygon, polygonMumbai } from 'wagmi/chains' +import { alchemyProvider } from 'wagmi/providers/alchemy' +import { publicProvider } from 'wagmi/providers/public' +import RainBowButton from './button' + +const { chains, publicClient } = configureChains( + [mainnet, polygon, polygonMumbai], + [alchemyProvider({ apiKey: process.env.REACT_APP_ALCHEMY_ID }), publicProvider()], +) + +const { connectors } = getDefaultWallets({ + appName: 'Wallet Connect With RainBow', + projectId: process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID, + chains, +}) + +const wagmiConfig = createConfig({ + autoConnect: true, + connectors, + publicClient, +}) + +export const WalletButton = () => { + return ( + + + + ) +} diff --git a/src/index.js b/src/index.js index d19a3bcd3..24e08e724 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ import { createRoot } from 'react-dom/client' import App from './App' import reportWebVitals from './reportWebVitals' import { Provider } from 'react-redux' -import store from './store' +import store from './redux/store' createRoot(document.getElementById('root')).render( diff --git a/src/redux/modules/init/saga.js b/src/redux/modules/init/saga.js new file mode 100644 index 000000000..879441993 --- /dev/null +++ b/src/redux/modules/init/saga.js @@ -0,0 +1,16 @@ +import { put, take, takeEvery } from 'redux-saga/effects' +import { watchAppNetwrok } from '../web3/slice' +import { initApp, initAppCompleted } from './slice' +export function* watchInitApp() { + try { + yield put(watchAppNetwrok()) + yield take(watchAppNetwrok) + yield put(initAppCompleted()) + } catch (e) { + console.log(e, 'error in init app') + } +} + +export function* initSagas() { + yield takeEvery(initApp, watchInitApp) +} diff --git a/src/redux/modules/init/slice.js b/src/redux/modules/init/slice.js new file mode 100644 index 000000000..59b8d5df0 --- /dev/null +++ b/src/redux/modules/init/slice.js @@ -0,0 +1,56 @@ +import { createSlice } from '@reduxjs/toolkit' +import { useDispatch, useSelector } from 'react-redux' +import { useCallback } from 'react' + +const initialState = { + sidebarShow: true, +} + +const initSlice = createSlice({ + name: 'init', + initialState, + reducers: { + setSidebarShow: (state) => { + state.sidebarShow = !state.sidebarShow + }, + initApp: (state) => { + state.loading = true + }, + initAppCompleted: (state) => { + state.loading = false + }, + }, +}) + +export function useToggleSidebar() { + const dispatch = useDispatch() + const sidebarShow = useSelector((state) => state.init.sidebarShow) + + const toggleSidebar = useCallback(() => { + dispatch(initSlice.actions.setSidebarShow()) + }, [dispatch]) + + return { sidebarShow, toggleSidebar } +} + +export function useInit() { + const initState = useSelector((state) => state.init) + const dispatch = useDispatch() + + const dispatchInit = useCallback(() => { + dispatch(initApp()) + }, [dispatch]) + + const dispatchInitCompleted = useCallback(() => { + dispatch(initAppCompleted()) + }, [dispatch]) + + return { + initState, + dispatchInit, + dispatchInitCompleted, + } +} + +export const { setSidebarShow, initApp, initAppCompleted } = initSlice.actions +export default initSlice.reducer diff --git a/src/redux/modules/userNonce/index.js b/src/redux/modules/userNonce/index.js new file mode 100644 index 000000000..437e83553 --- /dev/null +++ b/src/redux/modules/userNonce/index.js @@ -0,0 +1,44 @@ +import { useCallback } from 'react' +import { put, takeEvery, select } from 'redux-saga/effects' +import ReduxFetchState from 'redux-fetch-state' +import { getAccount, signMessage } from '@wagmi/core' +import { useDispatch, useSelector } from 'react-redux' +import apiPlugin from '../../../services/api' +import { userSignActions } from '../userSign' + +const { actions, actionTypes, reducer } = new ReduxFetchState('userNonce') + +export function* watchUserNonce(action) { + const { base_url } = yield select((state) => state.web3?.config || {}) + const { address } = action.payload + try { + const response = yield apiPlugin.getData(`${base_url}/nonce/${address}`) + const { message } = response + const signature = yield signMessage({ message: message }) + yield put(actions.loadSuccess(response)) + yield put(userSignActions.load({ address, signature })) + } catch (e) { + yield put(actions.loadFailure(e)) + } +} + +export function* userNonceSagas() { + yield takeEvery(actionTypes.load, watchUserNonce) +} + +export function useGetNonce() { + const dispatch = useDispatch() + const { data: userNonce, ...userNonceState } = useSelector((state) => state.userSign) + + const dispatchGetNonce = useCallback(() => { + const { address } = getAccount() + dispatch(actions.load({ address })) + }, [dispatch]) + return { userNonce, ...userNonceState, dispatchGetNonce } +} + +export { + reducer as userNonceReducer, + actions as userNonceActions, + actionTypes as userNonceActionTypes, +} diff --git a/src/redux/modules/userSign/index.js b/src/redux/modules/userSign/index.js new file mode 100644 index 000000000..f9faa6bc8 --- /dev/null +++ b/src/redux/modules/userSign/index.js @@ -0,0 +1,40 @@ +import { put, takeEvery, select } from 'redux-saga/effects' +import { useDispatch, useSelector } from 'react-redux' +import { useCallback } from 'react' +import ReduxFetchState from 'redux-fetch-state' +import apiPlugin from '../../../services/api' +const { actions, actionTypes, reducer } = new ReduxFetchState('userSign') + +export function* watchUserSign(action) { + const { base_url } = yield select((state) => state.web3.config) + const { address, signature } = action.payload + try { + const response = yield apiPlugin.postData(`${base_url}/login/${address}`, { + signature: signature, + }) + yield put(actions.loadSuccess(response)) + } catch (e) { + yield put(actions.loadFailure(e)) + } +} + +export function* userSignSagas() { + yield takeEvery(actionTypes.load, watchUserSign) +} + +export function useUserSign() { + const dispatch = useDispatch() + const { data: userSign, ...userSignState } = useSelector((state) => state.userSign) + + const dispatchRemoveToken = useCallback(() => { + dispatch(actions.loadSuccess([])) + }, [dispatch]) + + return { userSign, ...userSignState, dispatchRemoveToken } +} + +export { + reducer as userSignReducer, + actions as userSignActions, + actionTypes as userSignActionTypes, +} diff --git a/src/redux/modules/web3/saga.js b/src/redux/modules/web3/saga.js new file mode 100644 index 000000000..e03fdbd00 --- /dev/null +++ b/src/redux/modules/web3/saga.js @@ -0,0 +1,41 @@ +import { takeEvery, call, put } from 'redux-saga/effects' +import { watchNetwork } from '@wagmi/core' +import { setIsSupportedNetwork, watchAppNetwrok } from './slice' +import blockChainConfig from '../../../services/config' + +function* handleNetworkChange({ chain }) { + try { + const chainConfig = blockChainConfig[chain?.id] + if (chainConfig) { + yield put(setIsSupportedNetwork(true)) + yield put(watchAppNetwrok(chainConfig)) + } else { + yield put(setIsSupportedNetwork(false)) + } + } catch (error) { + console.error('Error while handling network change:', error) + } +} + +function watchNetworkPromise() { + return new Promise((resolve, reject) => { + watchNetwork(({ chain }) => { + if (chain && !chain.unsupported) { + resolve({ chain }) + } + }) + }) +} + +export function* watchCurrentNetwork() { + try { + const { chain } = yield call(watchNetworkPromise) + yield call(handleNetworkChange, { chain }) + } catch (error) { + console.error('Error while watching network:', error) + } +} + +export function* web3Saga() { + yield takeEvery(watchAppNetwrok, watchCurrentNetwork) +} diff --git a/src/redux/modules/web3/slice.js b/src/redux/modules/web3/slice.js new file mode 100644 index 000000000..eb2518728 --- /dev/null +++ b/src/redux/modules/web3/slice.js @@ -0,0 +1,28 @@ +import { useSelector } from 'react-redux' +import { createSlice } from '@reduxjs/toolkit' + +const initialState = { + config: null, + isSupportedNetwork: false, +} + +const web3Slice = createSlice({ + name: 'web3', + initialState, + reducers: { + watchAppNetwrok: (state, action) => { + state.config = action.payload + }, + setIsSupportedNetwork: (state, action) => { + state.isSupportedNetwork = action.payload + }, + }, +}) + +export function useWeb3() { + const web3 = useSelector((state) => state.web3) + return { web3 } +} + +export const { watchAppNetwrok, setIsSupportedNetwork } = web3Slice.actions +export default web3Slice.reducer diff --git a/src/redux/reducer.js b/src/redux/reducer.js new file mode 100644 index 000000000..3306f493e --- /dev/null +++ b/src/redux/reducer.js @@ -0,0 +1,14 @@ +import { combineReducers } from 'redux' +import initReducer from './modules/init/slice' +import web3Reducer from './modules/web3/slice' +import { userSignReducer } from './modules/userSign' +import { userNonceReducer } from './modules/userNonce' + +const rootReducer = combineReducers({ + init: initReducer, + userSign: userSignReducer, + userNonce: userNonceReducer, + web3: web3Reducer, +}) + +export default rootReducer diff --git a/src/redux/sagas.js b/src/redux/sagas.js new file mode 100644 index 000000000..12be1a46f --- /dev/null +++ b/src/redux/sagas.js @@ -0,0 +1,9 @@ +import { all } from 'redux-saga/effects' +import { userNonceSagas } from './modules/userNonce' +import { userSignSagas } from './modules/userSign' +import { initSagas } from './modules/init/saga' +import { web3Saga } from './modules/web3/saga' + +export default function* rootSaga() { + yield all([initSagas(), userNonceSagas(), userSignSagas(), web3Saga()]) +} diff --git a/src/redux/store.js b/src/redux/store.js new file mode 100644 index 000000000..7f1afaa25 --- /dev/null +++ b/src/redux/store.js @@ -0,0 +1,16 @@ +import { configureStore } from '@reduxjs/toolkit' +import createSagaMiddleware from 'redux-saga' +import rootReducer from './reducer' +import rootSaga from './sagas' + +const sagaMiddleware = createSagaMiddleware() + +const store = configureStore({ + reducer: rootReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ thunk: false, serializableCheck: false }).prepend(sagaMiddleware), +}) + +sagaMiddleware.run(rootSaga) + +export default store diff --git a/src/scss/_custom.scss b/src/scss/_custom.scss index 15d367af4..c53e63d02 100644 --- a/src/scss/_custom.scss +++ b/src/scss/_custom.scss @@ -1 +1,8 @@ // Here you can add other styles + + +// Size // +.w-15-icon { + width: 15px; + height: 15px; +} \ No newline at end of file diff --git a/src/services/api.js b/src/services/api.js new file mode 100644 index 000000000..d4b9402c8 --- /dev/null +++ b/src/services/api.js @@ -0,0 +1,53 @@ +import axios from 'axios' + +class ReactApiPlugin { + setAuthToken(token) { + axios.defaults.headers.common['Authorization'] = `Bearer ${token}` + } + + clearAuthToken() { + delete axios.defaults.headers.common['Authorization'] + } + + async putData(url, data, config = {}) { + try { + const response = await axios.put(url, data, config) + return response.data + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message) + } else { + throw new Error('Network error') + } + } + } + + async getData(url, config = {}) { + try { + const response = await axios.get(url, config) + return response.data + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message) + } else { + throw new Error('Network error') + } + } + } + + async postData(url, data, config = {}) { + try { + const response = await axios.post(url, data, config) + return response.data + } catch (error) { + if (error.response) { + throw new Error(error.response.data.message) + } else { + throw new Error('Network error') + } + } + } +} + +const apiPluginInstance = new ReactApiPlugin() +export default apiPluginInstance diff --git a/src/services/config.js b/src/services/config.js new file mode 100644 index 000000000..331ef028d --- /dev/null +++ b/src/services/config.js @@ -0,0 +1,55 @@ +const blockChainConfig = { + 137: { + contracts: { + TreeFactory: { + address: 'a', + abi: '', + }, + Paymaster: { + address: '', + abi: '', + }, + Planter: { + address: '', + abi: '', + }, + }, + base_url: process.env.REACT_APP_MAIN_MATIC_BASE_URL, + }, + 1: { + contracts: { + TreeFactory: { + address: 'b', + abi: '', + }, + Paymaster: { + address: '', + abi: '', + }, + Planter: { + address: '', + abi: '', + }, + }, + base_url: process.env.REACT_APP_MAIN_ETHERIUM_BASE_URL, + }, + 800001: { + contracts: { + TreeFactory: { + address: 'b', + abi: '', + }, + Paymaster: { + address: '', + abi: '', + }, + Planter: { + address: '', + abi: '', + }, + }, + base_url: process.env.REACT_APP_TEST_MATIC_BASE_URL, + }, +} + +export default blockChainConfig