diff --git a/README.md b/README.md
index 1185d8047..3dba05b94 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,18 @@ CoreUI is meant to be the UX game changer. Pure & transparent code is devoid of
* [Support CoreUI Development](#support-coreui-development)
* [Copyright and License](#copyright-and-license)
+## Login and Connect Wallet
+
+If you want to add login and connect wallet functionality to your application, you can use the Wagmi library. Wagmi is a collection of React Hooks that makes it easy to work with Ethereum, including connecting wallets, displaying ENS and balance information, signing messages, interacting with contracts, and more.
+
+To get started with Wagmi, you can follow these steps:
+
+1. Install the Wagmi library and the necessary dependencies in your React project:
+
+ ```bash
+ npm install wagmi viem
+ You can read more about that [here](https://wagmi.sh)
+
## Versions
* [CoreUI Free Bootstrap Admin Template](https://github.com/coreui/coreui-free-bootstrap-admin-template)
diff --git a/package.json b/package.json
index d6e40d776..a92a84a13 100644
--- a/package.json
+++ b/package.json
@@ -26,20 +26,37 @@
"@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",
+ "@coreui/react": "^4.6.0",
+ "@coreui/react-chartjs": "^2.1.2",
+ "@coreui/utils": "^2.0.1",
+ "@web3-react/core": "^8.2.0",
+ "@web3-react/injected-connector": "^6.0.7",
+ "@web3-react/walletconnect": "^8.2.0",
+ "@web3modal/ethereum": "^2.4.5",
+ "@web3modal/react": "^2.4.5",
+ "@web3modal/sign-react": "^2.4.7",
"chart.js": "^3.9.1",
"classnames": "^2.3.2",
- "core-js": "^3.31.0",
+ "core-js": "^3.29.0",
+ "ethers": "^6.6.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-app-polyfill": "^3.0.0",
"react-dom": "^18.2.0",
+ "react-ranger": "^2.1.0",
"react-redux": "^8.1.1",
"react-router-dom": "^6.14.0",
- "redux": "4.2.1",
- "simplebar-react": "^2.4.3"
+ "react-toastify": "^9.1.3",
+ "redux": "^4.2.1",
+ "redux-persist": "^6.0.0",
+ "redux-saga": "^1.2.3",
+ "redux-thunk": "^2.4.2",
+ "simplebar-react": "^2.4.3",
+ "viem": "^1.0.7",
+ "wagmi": "^1.2.0",
+ "web3": "^4.0.2",
+ "web3-core": "^4.0.1",
+ "web3-errors": "^1.0.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
@@ -56,4 +73,4 @@
"node": ">=10",
"npm": ">=6"
}
-}
+}
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
index 7c2488188..8fbf1e85a 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,6 +1,9 @@
import React, { Component, Suspense } from 'react'
+import { Provider } from 'react-redux'
import { HashRouter, Route, Routes } from 'react-router-dom'
import './scss/style.scss'
+import store from './redux/store'
+
const loading = (
@@ -20,17 +23,19 @@ const Page500 = React.lazy(() => import('./views/pages/page500/Page500'))
class App extends Component {
render() {
return (
-
-
-
- } />
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
)
}
}
diff --git a/src/assets/images/tokens/arbitrum.svg b/src/assets/images/tokens/arbitrum.svg
new file mode 100644
index 000000000..d439bc6f6
--- /dev/null
+++ b/src/assets/images/tokens/arbitrum.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/assets/images/tokens/celo.svg b/src/assets/images/tokens/celo.svg
new file mode 100644
index 000000000..bd24e97d7
--- /dev/null
+++ b/src/assets/images/tokens/celo.svg
@@ -0,0 +1,11 @@
+
+
+
diff --git a/src/assets/images/tokens/eth.png b/src/assets/images/tokens/eth.png
new file mode 100644
index 000000000..760946e76
Binary files /dev/null and b/src/assets/images/tokens/eth.png differ
diff --git a/src/assets/images/tokens/polygon.svg b/src/assets/images/tokens/polygon.svg
new file mode 100644
index 000000000..a5bb6124f
--- /dev/null
+++ b/src/assets/images/tokens/polygon.svg
@@ -0,0 +1,16 @@
+
+
+
diff --git a/src/components/AppHeader.js b/src/components/AppHeader.js
index dd5f544e3..e0d226a67 100644
--- a/src/components/AppHeader.js
+++ b/src/components/AppHeader.js
@@ -1,6 +1,7 @@
-import React from 'react'
-import { NavLink } from 'react-router-dom'
-import { useSelector, useDispatch } from 'react-redux'
+import React from 'react';
+import { NavLink } from 'react-router-dom';
+import { useSelector, useDispatch } from 'react-redux';
+import { setSidebarShow } from '../redux/actions';
import {
CContainer,
CHeader,
@@ -10,24 +11,24 @@ import {
CHeaderToggler,
CNavLink,
CNavItem,
-} from '@coreui/react'
-import CIcon from '@coreui/icons-react'
-import { cilBell, cilEnvelopeOpen, cilList, cilMenu } from '@coreui/icons'
+} from '@coreui/react';
+import CIcon from '@coreui/icons-react';
+import { cilBell, cilEnvelopeOpen, cilList, cilMenu } from '@coreui/icons';
-import { AppBreadcrumb } from './index'
-import { AppHeaderDropdown } from './header/index'
-import { logo } from 'src/assets/brand/logo'
+import { AppBreadcrumb } from './index';
+import { logo } from 'src/assets/brand/logo';
+import WagmiAuth from './header/wagmi'; // Import the WagmiAuth component
const AppHeader = () => {
- const dispatch = useDispatch()
- const sidebarShow = useSelector((state) => state.sidebarShow)
+ const dispatch = useDispatch();
+ const sidebarShow = useSelector((state) => state.sidebarShow);
return (
dispatch({ type: 'set', sidebarShow: !sidebarShow })}
+ onClick={() => dispatch(setSidebarShow(!sidebarShow))}
>
@@ -47,6 +48,10 @@ const AppHeader = () => {
Settings
+
+
+ {/* Render the WagmiAuth component */}
+
@@ -64,16 +69,13 @@ const AppHeader = () => {
-
-
-
- )
-}
+ );
+};
-export default AppHeader
+export default AppHeader;
diff --git a/src/components/header/auth/ChainDropdown.js b/src/components/header/auth/ChainDropdown.js
new file mode 100644
index 000000000..1465f203c
--- /dev/null
+++ b/src/components/header/auth/ChainDropdown.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import supportedNetwork from './SupportedNetwork';
+import { CAvatar, CDropdown, CDropdownToggle, CDropdownMenu, CDropdownItem } from '@coreui/react';
+
+const ChainDropdown = ({ chains, chain, switchNetwork, isLoading, pendingChainId }) => {
+ const findToken = (name) => {
+ return supportedNetwork.find((x) => x.name === name);
+ };
+
+ return (
+
+
+ {findToken(chain.name) ? '' : 'Unsupported Network Choosen'}
+
+
+
+ {chains.map((x) => (
+ switchNetwork?.(x.id)}
+ >
+
+ {x.name}
+ {isLoading && pendingChainId === x.id && ' (switching)'}
+
+ ))}
+
+
+ );
+};
+
+export default ChainDropdown;
diff --git a/src/components/header/auth/SupportedNetwork.js b/src/components/header/auth/SupportedNetwork.js
new file mode 100644
index 000000000..9f17a65ed
--- /dev/null
+++ b/src/components/header/auth/SupportedNetwork.js
@@ -0,0 +1,21 @@
+// supportedNetwork.js
+import ethIcon from '../../../assets/images/tokens/eth.png';
+import arbitIcon from '../../../assets/images/tokens/arbitrum.svg';
+import maticIcon from '../../../assets/images/tokens/polygon.svg';
+
+const supportedNetwork = [
+ {
+ name: 'Ethereum',
+ cover: ethIcon,
+ },
+ {
+ name: 'Arbitrum One',
+ cover: arbitIcon,
+ },
+ {
+ name: 'Polygon',
+ cover: maticIcon,
+ },
+];
+
+export default supportedNetwork;
diff --git a/src/components/header/auth/WalletDropdown.js b/src/components/header/auth/WalletDropdown.js
new file mode 100644
index 000000000..12031c207
--- /dev/null
+++ b/src/components/header/auth/WalletDropdown.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import { CAvatar, CBadge, CDropdown, CDropdownMenu, CDropdownItem, CDropdownToggle } from '@coreui/react';
+
+const WalletDropdown = ({ address, open, signIn, visible, status }) => {
+ return (
+
+ {visible && (
+
+ New alerts
+
+ )}
+
+
+ {address}
+
+ {/* Dropdown Menu */}
+
+ Account
+ Sign in wallet
+
+
+ );
+};
+
+export default WalletDropdown;
diff --git a/src/components/header/auth/index.js b/src/components/header/auth/index.js
new file mode 100644
index 000000000..abeefb042
--- /dev/null
+++ b/src/components/header/auth/index.js
@@ -0,0 +1,115 @@
+import React, { useState, useEffect } from 'react';
+import { useWeb3Modal } from '@web3modal/react';
+import { useNetwork, useSwitchNetwork, useAccount } from 'wagmi';
+import { useDispatch, useSelector } from 'react-redux';
+import { setToken } from '../../../redux/actions';
+import { useSignMessage } from 'wagmi';
+import ChainDropdown from './ChainDropdown';
+import WalletDropdown from './WalletDropdown';
+import supportedNetwork from './SupportedNetwork';
+import { getNonce, login } from '../../../services/apiServices';
+import { CButton } from '@coreui/react';
+import { ToastContainer, toast } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
+
+const ConnectWalletButton = () => {
+ const [visible, setVisible] = useState(false);
+ const { chain } = useNetwork();
+ const { open } = useWeb3Modal();
+ const { chains, isLoading, pendingChainId, switchNetwork } = useSwitchNetwork();
+ const { address, status } = useAccount();
+ const { data, error, signMessage } = useSignMessage();
+ const dispatch = useDispatch();
+ const token = useSelector((state) => state.token);
+
+ const findToken = (name) => {
+ return supportedNetwork.find((x) => x.name === name);
+ };
+
+ const handleDisconnect = () => {
+ setVisible(false);
+ dispatch(setToken('')); // Reset token in store
+ };
+
+ useEffect(() => {
+ if (!address) {
+ handleDisconnect();
+ }
+ }, [address, handleDisconnect]);
+
+ const signIn = async () => {
+ try {
+ const response = await getNonce(address);
+ if (response) {
+ try {
+ const message = response.message;
+ await signMessage({ message: message });
+ if (!error && data !== undefined) {
+ getToken(data, address);
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ toast.error('An error occurred. Please try again later.');
+ }
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ toast.error('An error occurred. Please try again later.');
+ }
+ };
+
+ const getToken = async (signature, address) => {
+ try {
+ if (signature) {
+ const response = await login(address, signature);
+ if (response) {
+ setVisible(true);
+ dispatch(setToken(response.access_token));
+ } else {
+ // Handle the error condition if needed
+ }
+ }
+ } catch (error) {
+ console.log('Error:', error);
+ }
+ };
+
+ return (
+ <>
+
+
+ {!address && (
+ open()}
+ color="primary"
+ className={address ? 'w-50 text-truncate h-75 rounded-pill' : 'w-auto rounded-pill'}
+ >
+ Connect
+
+ )}
+ {address && (
+
+ )}
+ {chain && (
+
+ )}
+
+ >
+ );
+};
+
+export default ConnectWalletButton;
diff --git a/src/components/header/wagmi/index.js b/src/components/header/wagmi/index.js
new file mode 100644
index 000000000..3245fbc2c
--- /dev/null
+++ b/src/components/header/wagmi/index.js
@@ -0,0 +1,29 @@
+import React from 'react';
+import ConnectWalletButton from '../auth';
+import { EthereumClient, w3mConnectors, w3mProvider } from '@web3modal/ethereum';
+import { Web3Modal } from '@web3modal/react';
+import { configureChains, createConfig, WagmiConfig } from 'wagmi';
+import { arbitrum, mainnet, polygon } from 'wagmi/chains';
+
+const chains = [arbitrum, mainnet, polygon];
+const projectId = 'Your Projcet ID';
+
+const { publicClient } = configureChains(chains, [w3mProvider({ projectId })]);
+const wagmiConfig = createConfig({
+ autoConnect: true,
+ connectors: w3mConnectors({ projectId, version: 1, chains }),
+ publicClient
+});
+
+const ethereumClient = new EthereumClient(wagmiConfig, chains);
+
+const WagmiAuth = () => {
+ return (
+
+
+
+
+ );
+};
+
+export default WagmiAuth;
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/actionTypes.js b/src/redux/actionTypes.js
new file mode 100644
index 000000000..ba3b8573f
--- /dev/null
+++ b/src/redux/actionTypes.js
@@ -0,0 +1,4 @@
+// actionTypes.js
+export const SET_TOKEN = 'SET_TOKEN';
+export const SET_USER = 'SET_USER';
+export const SET_SIDEBAR_SHOW = 'SET_SIDEBAR_SHOW';
diff --git a/src/redux/actions.js b/src/redux/actions.js
new file mode 100644
index 000000000..9c3758240
--- /dev/null
+++ b/src/redux/actions.js
@@ -0,0 +1,22 @@
+// actions.js
+// Import action types
+import { SET_TOKEN, SET_USER, SET_SIDEBAR_SHOW } from './actionTypes';
+
+
+// Action creators
+export const setToken = (token) => ({
+ type: SET_TOKEN,
+ payload: token
+});
+
+export const setUser = (user) => ({
+ type: SET_USER,
+ payload: user
+});
+
+export const setSidebarShow = (show) => {
+ return {
+ type: SET_SIDEBAR_SHOW,
+ payload: show,
+ };
+};
\ No newline at end of file
diff --git a/src/redux/reducer.js b/src/redux/reducer.js
new file mode 100644
index 000000000..a33c65b30
--- /dev/null
+++ b/src/redux/reducer.js
@@ -0,0 +1,35 @@
+// reducer.js
+// Import action types
+import { SET_TOKEN, SET_USER, SET_SIDEBAR_SHOW } from './actionTypes';
+
+// Initial state
+const initialState = {
+ sidebarShow: true,
+ token: null,
+ user: null
+};
+
+// Reducer
+const reducer = (state = initialState, action) => {
+ switch (action.type) {
+ case SET_TOKEN:
+ return {
+ ...state,
+ token: action.payload
+ };
+ case SET_USER:
+ return {
+ ...state,
+ user: action.payload
+ };
+ case SET_SIDEBAR_SHOW:
+ return {
+ ...state,
+ sidebarShow: action.payload
+ };
+ default:
+ return state;
+ }
+};
+
+export default reducer;
diff --git a/src/redux/store.js b/src/redux/store.js
new file mode 100644
index 000000000..1ee3a019c
--- /dev/null
+++ b/src/redux/store.js
@@ -0,0 +1,8 @@
+import { createStore, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import reducer from './reducer';
+
+// Create the store
+const store = createStore(reducer, applyMiddleware(thunk));
+
+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..9e8ba152a
--- /dev/null
+++ b/src/services/api.js
@@ -0,0 +1,52 @@
+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');
+ }
+ }
+ }
+}
+
+export default new ReactApiPlugin();
\ No newline at end of file
diff --git a/src/services/apiServices.js b/src/services/apiServices.js
new file mode 100644
index 000000000..5777a078c
--- /dev/null
+++ b/src/services/apiServices.js
@@ -0,0 +1,24 @@
+import apiPlugin from './api';
+const API_URL = "https://nestapi.treejer.com"
+
+export const getNonce = async (address) => {
+ try {
+ const response = await apiPlugin.getData(`${API_URL}/nonce/${address}`);
+ return response;
+ } catch (error) {
+ console.log('Error:', error);
+ throw error;
+ }
+};
+
+export const login = async (address, signature) => {
+ try {
+ const response = await apiPlugin.postData(`${API_URL}/login/${address}`, {
+ signature: signature,
+ });
+ return response;
+ } catch (error) {
+ console.log('Error:', error);
+ throw error;
+ }
+};
diff --git a/src/store.js b/src/store.js
deleted file mode 100644
index ab446364c..000000000
--- a/src/store.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { createStore } from 'redux'
-
-const initialState = {
- sidebarShow: true,
-}
-
-const changeState = (state = initialState, { type, ...rest }) => {
- switch (type) {
- case 'set':
- return { ...state, ...rest }
- default:
- return state
- }
-}
-
-const store = createStore(changeState)
-export default store