From 8b596861d59f8b523fa4de90f66caeceb84eb867 Mon Sep 17 00:00:00 2001 From: mrholek Date: Thu, 29 Jun 2023 12:58:47 +0200 Subject: [PATCH 1/4] chore: update dependencies and devDependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @coreui/chartjs ^3.1.1 → ^3.1.2 @coreui/react ^4.6.0 → ^4.8.0 @coreui/react-chartjs ^2.1.2 → ^2.1.3 @coreui/utils ^2.0.1 → ^2.0.2 core-js ^3.29.0 → ^3.31.0 eslint-config-prettier ^8.7.0 → ^8.8.0 prettier 2.8.4 → 2.8.8 react-redux ^8.0.5 → ^8.1.1 react-router-dom ^6.8.2 → ^6.14.0 sass ^1.58.3 → ^1.63.6 web-vitals ^3.1.1 → ^3.3.2 --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index da08efaab..d6e40d776 100644 --- a/package.json +++ b/package.json @@ -22,22 +22,22 @@ "test:debug": "react-scripts --inspect-brk test --runInBand" }, "dependencies": { - "@coreui/chartjs": "^3.1.1", + "@coreui/chartjs": "^3.1.2", "@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", + "@coreui/react": "^4.9.0-rc.0", + "@coreui/react-chartjs": "^2.1.3", + "@coreui/utils": "^2.0.2", "chart.js": "^3.9.1", "classnames": "^2.3.2", - "core-js": "^3.29.0", + "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.0.5", - "react-router-dom": "^6.8.2", + "react-redux": "^8.1.1", + "react-router-dom": "^6.14.0", "redux": "4.2.1", "simplebar-react": "^2.4.3" }, @@ -45,12 +45,12 @@ "@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-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", - "prettier": "2.8.4", + "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", From 3ee6de47c15098c32a50857b6d146eec934b08b9 Mon Sep 17 00:00:00 2001 From: mrholek Date: Thu, 29 Jun 2023 13:07:07 +0200 Subject: [PATCH 2/4] chore: update comments --- public/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 @@ From 8c4d5d8a7045bd831265518098e84a7b4106a33f Mon Sep 17 00:00:00 2001 From: behnam Date: Sat, 29 Jul 2023 14:43:08 +0330 Subject: [PATCH 3/4] Rainbow (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add wagmi component auth to projcet * chore: update dependencies and devDependencies @coreui/chartjs ^3.1.1 → ^3.1.2 @coreui/react ^4.6.0 → ^4.8.0 @coreui/react-chartjs ^2.1.2 → ^2.1.3 @coreui/utils ^2.0.1 → ^2.0.2 core-js ^3.29.0 → ^3.31.0 eslint-config-prettier ^8.7.0 → ^8.8.0 prettier 2.8.4 → 2.8.8 react-redux ^8.0.5 → ^8.1.1 react-router-dom ^6.8.2 → ^6.14.0 sass ^1.58.3 → ^1.63.6 web-vitals ^3.1.1 → ^3.3.2 * chore: update comments * Add rainbow kit to project and configure that * Update gitignore and remove env file * Add redux, saga * Add axios to project * Update redme file * Update package.json file and install necessary packages * Add rainbow button to header and change sidebar show action * Update sidebar acions from redux * Update store address * Update env key name * Update README.md * Update README.md Add rainbow emoji * Remove unused svg * Remove unused files * Add remove token when switch network * Refactor rainbow button code * Remove comment of code --------- Co-authored-by: mrholek Co-authored-by: Amir Habibzadeh --- .env | 2 - .env.example | 5 + .gitignore | 1 + README.md | 158 ++++----------------------- package.json | 57 +++++----- src/components/AppHeader.js | 15 ++- src/components/AppSidebar.js | 26 +---- src/components/rainbow/button.js | 60 ++++++++++ src/components/rainbow/index.js | 34 ++++++ src/index.js | 2 +- src/redux/modules/init/index.js | 31 ++++++ src/redux/modules/userNonce/index.js | 44 ++++++++ src/redux/modules/userSign/index.js | 40 +++++++ src/redux/reducer.js | 12 ++ src/redux/sagas.js | 7 ++ src/redux/store.js | 16 +++ src/scss/_custom.scss | 7 ++ src/services/api.js | 53 +++++++++ 18 files changed, 377 insertions(+), 193 deletions(-) delete mode 100644 .env create mode 100644 .env.example create mode 100644 src/components/rainbow/button.js create mode 100644 src/components/rainbow/index.js create mode 100644 src/redux/modules/init/index.js create mode 100644 src/redux/modules/userNonce/index.js create mode 100644 src/redux/modules/userSign/index.js create mode 100644 src/redux/reducer.js create mode 100644 src/redux/sagas.js create mode 100644 src/redux/store.js create mode 100644 src/services/api.js 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..030e6bd5b --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +PORT=3000 +CHOKIDAR_USEPOLLING=true +REACT_APP_BASE_URL="" +REACT_APP_WALLET_CONNECT_PROJECT_ID="" +REACT_APP_ALCHEMY_ID="" \ 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..f0cd673b6 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,20 @@ -[![@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. + +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 +60,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 d6e40d776..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.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", - "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", + "@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.8.0", - "eslint-plugin-prettier": "^4.2.1", + "@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.63.6", - "web-vitals": "^3.3.2" + "sass": "1.63.6", + "web-vitals": "3.3.2" }, "engines": { "node": ">=10", "npm": ">=6" } -} +} \ No newline at end of file diff --git a/src/components/AppHeader.js b/src/components/AppHeader.js index dd5f544e3..6649a915e 100644 --- a/src/components/AppHeader.js +++ b/src/components/AppHeader.js @@ -1,6 +1,5 @@ import React from 'react' import { NavLink } from 'react-router-dom' -import { useSelector, useDispatch } from 'react-redux' import { CContainer, CHeader, @@ -13,22 +12,19 @@ import { } from '@coreui/react' import CIcon from '@coreui/icons-react' import { cilBell, cilEnvelopeOpen, cilList, cilMenu } from '@coreui/icons' - +import { useToggleSidebar } from '../redux/modules/init' 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() return ( - dispatch({ type: 'set', sidebarShow: !sidebarShow })} - > + @@ -47,6 +43,9 @@ const AppHeader = () => { Settings + + + diff --git a/src/components/AppSidebar.js b/src/components/AppSidebar.js index a75bf6523..91437a159 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' 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..d7abfc4d3 --- /dev/null +++ b/src/components/rainbow/button.js @@ -0,0 +1,60 @@ +import React, { useEffect } from 'react' +import { CButton } from '@coreui/react' +import { useSelector } from 'react-redux' +import { RainbowKitProvider, ConnectButton } from '@rainbow-me/rainbowkit' +import { configureChains, useAccount, useNetwork } from 'wagmi' +import { mainnet, polygon, optimism, arbitrum, zora } from 'wagmi/chains' +import { alchemyProvider } from 'wagmi/providers/alchemy' +import { useGetNonce } from '../../redux/modules/userNonce' +import { useRemoveToken } 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, optimism, arbitrum, zora] +const providers = [alchemyProvider({ apiKey }), publicProvider()] +const { chains } = configureChains(supportedChains, providers) + +const RainbowButton = () => { + const { address } = useAccount() + const { chain } = useNetwork() + const { dispatchGetNonce } = useGetNonce() + const { dispatchRemoveToken } = useRemoveToken() + const userToken = useSelector((state) => state.userSign?.data?.access_token) + + useEffect(() => { + handleEffectLogic(address, chain, dispatchGetNonce, dispatchRemoveToken) + }, [address, chain, dispatchGetNonce, dispatchRemoveToken]) + + const handleEffectLogic = (address, chain, dispatchGetNonce, dispatchRemoveToken) => { + if (address) { + dispatchGetNonce(address) + } else { + dispatchRemoveToken() + } + if (chain) { + dispatchRemoveToken() + } + } + + const handleSignInWallet = () => { + handleEffectLogic(address, chain, dispatchGetNonce, dispatchRemoveToken) + } + + const showSignInWalletButton = !userToken && address + return ( + <> + {showSignInWalletButton && ( + + 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..9cceabdfd --- /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, optimism, arbitrum, zora } 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, optimism, arbitrum, zora], + [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/index.js b/src/redux/modules/init/index.js new file mode 100644 index 000000000..e01581da4 --- /dev/null +++ b/src/redux/modules/init/index.js @@ -0,0 +1,31 @@ +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 + }, + }, +}) + +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 const { setSidebarShow } = 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..d9da600cc --- /dev/null +++ b/src/redux/modules/userNonce/index.js @@ -0,0 +1,44 @@ +import { useCallback } from 'react' +import { put, takeEvery } from 'redux-saga/effects' +import ReduxFetchState from 'redux-fetch-state' +import { getAccount, signMessage } from '@wagmi/core' +import { useDispatch } from 'react-redux' +import apiPlugin from '../../../services/api' +import { userSignActions } from '../userSign' +const API_URL = process.env.REACT_APP_BASE_URL + +const { actions, actionTypes, reducer } = new ReduxFetchState('userNonce') + +export function* watchUserNonce(action) { + const { address } = action.payload + try { + const response = yield apiPlugin.getData(`${API_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 dispatchGetNonce = useCallback(() => { + const { address } = getAccount() + dispatch(actions.load({ address })) + }, [dispatch]) + + return { 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..64ead47d7 --- /dev/null +++ b/src/redux/modules/userSign/index.js @@ -0,0 +1,40 @@ +import { put, takeEvery } from 'redux-saga/effects' +import { useCallback } from 'react' +import ReduxFetchState from 'redux-fetch-state' +import apiPlugin from '../../../services/api' +import { useDispatch } from 'react-redux' +const API_URL = process.env.REACT_APP_BASE_URL + +const { actions, actionTypes, reducer } = new ReduxFetchState('userSign') + +export function* watchUserSign(action) { + const { address, signature } = action.payload + try { + const response = yield apiPlugin.postData(`${API_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 useRemoveToken() { + const dispatch = useDispatch() + + const dispatchRemoveToken = useCallback(() => { + dispatch(actions.loadSuccess([])) + }, [dispatch]) + + return { dispatchRemoveToken } +} + +export { + reducer as userSignReducer, + actions as userSignActions, + actionTypes as userSignActionTypes, +} diff --git a/src/redux/reducer.js b/src/redux/reducer.js new file mode 100644 index 000000000..fa5403a07 --- /dev/null +++ b/src/redux/reducer.js @@ -0,0 +1,12 @@ +import { combineReducers } from 'redux' +import initReducer from './modules/init' +import { userSignReducer } from './modules/userSign' +import { userNonceReducer } from './modules/userNonce' + +const rootReducer = combineReducers({ + init: initReducer, + userSign: userSignReducer, + userNonce: userNonceReducer, +}) + +export default rootReducer diff --git a/src/redux/sagas.js b/src/redux/sagas.js new file mode 100644 index 000000000..be9a9280b --- /dev/null +++ b/src/redux/sagas.js @@ -0,0 +1,7 @@ +import { all } from 'redux-saga/effects' +import { userNonceSagas } from './modules/userNonce' +import { userSignSagas } from './modules/userSign' + +export default function* rootSaga() { + yield all([userNonceSagas(), userSignSagas()]) +} 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 From 133f4d5886b8042bb063c613fbbf8622570f5183 Mon Sep 17 00:00:00 2001 From: behnam Date: Thu, 3 Aug 2023 14:25:15 +0330 Subject: [PATCH 4/4] Multichain (#9) * Multichain config added --------- Co-authored-by: Amir Habibzadeh --- .env.example | 17 +++++- .eslintrc.js | 2 +- README.md | 1 + src/components/AppHeader.js | 9 ++- src/components/AppSidebar.js | 2 +- src/components/rainbow/button.js | 42 ++++++-------- src/components/rainbow/index.js | 4 +- src/redux/modules/init/saga.js | 16 ++++++ src/redux/modules/init/{index.js => slice.js} | 27 ++++++++- src/redux/modules/userNonce/index.js | 12 ++-- src/redux/modules/userSign/index.js | 14 ++--- src/redux/modules/web3/saga.js | 41 ++++++++++++++ src/redux/modules/web3/slice.js | 28 ++++++++++ src/redux/reducer.js | 4 +- src/redux/sagas.js | 4 +- src/services/config.js | 55 +++++++++++++++++++ 16 files changed, 230 insertions(+), 48 deletions(-) create mode 100644 src/redux/modules/init/saga.js rename src/redux/modules/init/{index.js => slice.js} (53%) create mode 100644 src/redux/modules/web3/saga.js create mode 100644 src/redux/modules/web3/slice.js create mode 100644 src/services/config.js diff --git a/.env.example b/.env.example index 030e6bd5b..c0f017649 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,18 @@ PORT=3000 CHOKIDAR_USEPOLLING=true -REACT_APP_BASE_URL="" REACT_APP_WALLET_CONNECT_PROJECT_ID="" -REACT_APP_ALCHEMY_ID="" \ No newline at end of file +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/README.md b/README.md index f0cd673b6..47f911a93 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ We have extended the functionality of the original CoreUI template by adding web - 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. diff --git a/src/components/AppHeader.js b/src/components/AppHeader.js index 6649a915e..58ebba445 100644 --- a/src/components/AppHeader.js +++ b/src/components/AppHeader.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import { NavLink } from 'react-router-dom' import { CContainer, @@ -12,7 +12,7 @@ import { } from '@coreui/react' import CIcon from '@coreui/icons-react' import { cilBell, cilEnvelopeOpen, cilList, cilMenu } from '@coreui/icons' -import { useToggleSidebar } from '../redux/modules/init' +import { useToggleSidebar, useInit } from '../redux/modules/init/slice' import { AppBreadcrumb } from './index' import { AppHeaderDropdown } from './header/index' import { logo } from 'src/assets/brand/logo' @@ -20,6 +20,11 @@ import { WalletButton } from './rainbow/index' const AppHeader = () => { const { toggleSidebar } = useToggleSidebar() + const { dispatchInit } = useInit() + + useEffect(() => { + dispatchInit() + }, []) return ( diff --git a/src/components/AppSidebar.js b/src/components/AppSidebar.js index 91437a159..7d9a713d5 100644 --- a/src/components/AppSidebar.js +++ b/src/components/AppSidebar.js @@ -8,7 +8,7 @@ import { sygnet } from 'src/assets/brand/sygnet' import SimpleBar from 'simplebar-react' import 'simplebar/dist/simplebar.min.css' import navigation from '../_nav' -import { useToggleSidebar } from '../redux/modules/init' +import { useToggleSidebar } from '../redux/modules/init/slice' const AppSidebar = () => { const unfoldable = useSelector((state) => state.sidebarUnfoldable) diff --git a/src/components/rainbow/button.js b/src/components/rainbow/button.js index d7abfc4d3..9a4c591f9 100644 --- a/src/components/rainbow/button.js +++ b/src/components/rainbow/button.js @@ -1,56 +1,50 @@ import React, { useEffect } from 'react' import { CButton } from '@coreui/react' -import { useSelector } from 'react-redux' import { RainbowKitProvider, ConnectButton } from '@rainbow-me/rainbowkit' import { configureChains, useAccount, useNetwork } from 'wagmi' -import { mainnet, polygon, optimism, arbitrum, zora } from 'wagmi/chains' +import { mainnet, polygon, polygonMumbai } from 'wagmi/chains' import { alchemyProvider } from 'wagmi/providers/alchemy' import { useGetNonce } from '../../redux/modules/userNonce' -import { useRemoveToken } from '../../redux/modules/userSign' +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, optimism, arbitrum, zora] +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 } = useGetNonce() - const { dispatchRemoveToken } = useRemoveToken() - const userToken = useSelector((state) => state.userSign?.data?.access_token) + const { dispatchGetNonce, loading: userNonceLoading } = useGetNonce() + const { dispatchRemoveToken, userSign, loading: userSignLoading } = useUserSign() + const userToken = userSign?.access_token + const isLoading = userNonceLoading || userSignLoading useEffect(() => { - handleEffectLogic(address, chain, dispatchGetNonce, dispatchRemoveToken) - }, [address, chain, dispatchGetNonce, dispatchRemoveToken]) - - const handleEffectLogic = (address, chain, dispatchGetNonce, dispatchRemoveToken) => { - if (address) { - dispatchGetNonce(address) - } else { - dispatchRemoveToken() - } if (chain) { dispatchRemoveToken() } - } + }, [chain, dispatchRemoveToken]) - const handleSignInWallet = () => { - handleEffectLogic(address, chain, dispatchGetNonce, dispatchRemoveToken) + const handleSignInWallet = async () => { + try { + await dispatchGetNonce(address) + } catch (error) { + console.error('Error signing in:', error) + } } - const showSignInWalletButton = !userToken && address + const showSignInWalletButton = !userToken && address && !chain.unsupported return ( <> {showSignInWalletButton && ( - - Sign In Wallet + + {isLoading ? 'Signing In...' : 'Sign In Wallet'} )} - + diff --git a/src/components/rainbow/index.js b/src/components/rainbow/index.js index 9cceabdfd..1784d6c4c 100644 --- a/src/components/rainbow/index.js +++ b/src/components/rainbow/index.js @@ -3,13 +3,13 @@ import React from 'react' import { getDefaultWallets } from '@rainbow-me/rainbowkit' import { configureChains, createConfig, WagmiConfig } from 'wagmi' -import { mainnet, polygon, optimism, arbitrum, zora } from 'wagmi/chains' +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, optimism, arbitrum, zora], + [mainnet, polygon, polygonMumbai], [alchemyProvider({ apiKey: process.env.REACT_APP_ALCHEMY_ID }), publicProvider()], ) 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/index.js b/src/redux/modules/init/slice.js similarity index 53% rename from src/redux/modules/init/index.js rename to src/redux/modules/init/slice.js index e01581da4..59b8d5df0 100644 --- a/src/redux/modules/init/index.js +++ b/src/redux/modules/init/slice.js @@ -13,6 +13,12 @@ const initSlice = createSlice({ setSidebarShow: (state) => { state.sidebarShow = !state.sidebarShow }, + initApp: (state) => { + state.loading = true + }, + initAppCompleted: (state) => { + state.loading = false + }, }, }) @@ -27,5 +33,24 @@ export function useToggleSidebar() { return { sidebarShow, toggleSidebar } } -export const { setSidebarShow } = initSlice.actions +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 index d9da600cc..437e83553 100644 --- a/src/redux/modules/userNonce/index.js +++ b/src/redux/modules/userNonce/index.js @@ -1,18 +1,18 @@ import { useCallback } from 'react' -import { put, takeEvery } from 'redux-saga/effects' +import { put, takeEvery, select } from 'redux-saga/effects' import ReduxFetchState from 'redux-fetch-state' import { getAccount, signMessage } from '@wagmi/core' -import { useDispatch } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import apiPlugin from '../../../services/api' import { userSignActions } from '../userSign' -const API_URL = process.env.REACT_APP_BASE_URL 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(`${API_URL}/nonce/${address}`) + const response = yield apiPlugin.getData(`${base_url}/nonce/${address}`) const { message } = response const signature = yield signMessage({ message: message }) yield put(actions.loadSuccess(response)) @@ -28,13 +28,13 @@ export function* userNonceSagas() { 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 { dispatchGetNonce } + return { userNonce, ...userNonceState, dispatchGetNonce } } export { diff --git a/src/redux/modules/userSign/index.js b/src/redux/modules/userSign/index.js index 64ead47d7..f9faa6bc8 100644 --- a/src/redux/modules/userSign/index.js +++ b/src/redux/modules/userSign/index.js @@ -1,16 +1,15 @@ -import { put, takeEvery } from 'redux-saga/effects' +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' -import { useDispatch } from 'react-redux' -const API_URL = process.env.REACT_APP_BASE_URL - 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(`${API_URL}/login/${address}`, { + const response = yield apiPlugin.postData(`${base_url}/login/${address}`, { signature: signature, }) yield put(actions.loadSuccess(response)) @@ -23,14 +22,15 @@ export function* userSignSagas() { yield takeEvery(actionTypes.load, watchUserSign) } -export function useRemoveToken() { +export function useUserSign() { const dispatch = useDispatch() + const { data: userSign, ...userSignState } = useSelector((state) => state.userSign) const dispatchRemoveToken = useCallback(() => { dispatch(actions.loadSuccess([])) }, [dispatch]) - return { dispatchRemoveToken } + return { userSign, ...userSignState, dispatchRemoveToken } } export { 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 index fa5403a07..3306f493e 100644 --- a/src/redux/reducer.js +++ b/src/redux/reducer.js @@ -1,5 +1,6 @@ import { combineReducers } from 'redux' -import initReducer from './modules/init' +import initReducer from './modules/init/slice' +import web3Reducer from './modules/web3/slice' import { userSignReducer } from './modules/userSign' import { userNonceReducer } from './modules/userNonce' @@ -7,6 +8,7 @@ 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 index be9a9280b..12be1a46f 100644 --- a/src/redux/sagas.js +++ b/src/redux/sagas.js @@ -1,7 +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([userNonceSagas(), userSignSagas()]) + yield all([initSagas(), userNonceSagas(), userSignSagas(), web3Saga()]) } 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