# dApp Scaffolding with React.
<br />
<br />
<p align="center" style="text-align: center">
<img src="https://i.imgur.com/7kwKYY0.gif" alt="Image" width="60%" />
</p>
<br />
<br />
### Getting Started 🚀
1. ```yarn``` or ```npm i```
2. Read this README file 😎
<br />
### Contents
1. [ESLint and Prettier - ready](https://github.com/MelodicCrypter/react-dapp-scaffolding#eslint-and-prettier---ready-optional-)
2. [ENV Setting](https://github.com/MelodicCrypter/react-dapp-scaffolding#env-)
3. [Reusable Component (Preview)](https://github.com/MelodicCrypter/react-dapp-scaffolding#reusable-components-tldr-)
4. [Custom Hooks (Preview)](https://github.com/MelodicCrypter/react-dapp-scaffolding#custom-hooks-tldr-)
5. [EthersProvider](https://github.com/MelodicCrypter/react-dapp-scaffolding#ethersprovider-)
6. [MessageNet](https://github.com/MelodicCrypter/react-dapp-scaffolding#messagenet-)
7. [Components Directory Structure](https://github.com/MelodicCrypter/react-dapp-scaffolding#components-directory-structure-)
8. [Custom Hooks Properties](https://github.com/MelodicCrypter/react-dapp-scaffolding#custom-hooks-properties-)
9. [Reusable Components Properties](https://github.com/MelodicCrypter/react-dapp-scaffolding#reusable-components-properties-)
<br />
### ESLint and Prettier - ready (_optional_) 🪝
---
If you want to use ESLint and Prettier while developing just copy sample settings inside
```sample-eslint.txt``` and ```sample-prettier.txt``` then create these two files:
```
touch .eslintrc .prettierrc
```
However, if you want to opt out in using these, just uninstall the Dev Dependencies:
```
yarn remove @babel/eslint-parser @babel/preset-react eslint eslint-config-airbnb eslint-config-prettier eslint-config-react-app eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks prettier pretty-quick
```
##### NOTE: PLEASE ALSO CHECK YOUR IDE SETTINGS. 👋🏻
<br />
### ENV 🪝
---
An Infura ID is needed for Wallet Connect.
<br />
```
REACT_APP_INFURA_ID=
REACT_APP_CONTRACT_ADDRESS=
```
<br />
### Reusable Components (TL;DR) 🔥
---
As of now, these are the reusable components in this scaffolding:
* ```<Connector />```
* This is a button that has methods for connecting/disconnecting wallet.
* ```<Crementor />```
* This is a button that will increment/decrement quantity to be minted.
* ```<Message />```
* Used for notification banners
* ```<NiceButton />```
* A button which you can add icon or text or events
* ```<Collapsible />```
* Can be toggled on/off, mostly used for FAQ
Jump to bottom section to read more about these components.
<br />
### Custom Hooks (TL;DR) 🔥
---
As of now, these are the custom hooks available:
* ```useSignature```
* Allows to easily sign a message using provider (e.g., Metamask)
Jump to bottom section to read more about these components.
<br />
### EthersProvider 🌴
---
The EthersProvider is the main component. It will return instances of 'ether' and 'web3Modal', and its own ethersProvider util data. Purpose is to just pull the instances instead of always importing it. These are all the data that EthersProvider can return:
```javascript
{
ethers,
web3Modal,
address,
chainId,
provider,
signer,
isConnected,
isMetamaskInstalled,
ethersProvider: {
connect: () => {},
disconnect: () => {},
switchNetwork: () => {},
},
}
```
Also, the EthersProvider has a `askOnLoad` props. If it is set to `true` (*its default state*), on site's load it will trigger the Metamask to prompt asking the user to switch to Ethereum network. Setting it to `false` will prevent it from prompting. Check sample usage below.
<br />
There are two (2) main scenarios to implement initialization.
<br />
**SCENARIO A1**: You want to manually handle everything:
```javascript
const { ethers, web3Modal } = useContext(EthersContext);
// States
const [isConnected, setIsConnected] = useState(false);
const [userAccount, setUserAccount] = useState(null);
const [chainId, setChainId] = useState(null);
const [signer, setSigner] = useState(null);
const [provider, setProvider] = useState(null);
// Handlers
const onConnectHandler = async () => {
try {
const instance = await web3Modal.connect();
const provider = new ethers.providers.Web3Provider(instance);
const { chainId } = await provider.getNetwork();
const signer = await provider.getSigner();
const address = await signer.getAddress();
setProvider(provider);
setUserAccount(address);
setChainId(chainId);
setSigner(signer); // optional
setIsConnected(true);
} catch ({ message }) {
if (!message) return;
const errorMsg = message.toLowerCase();
if (errorMsg.includes('user rejected')) {
console.log('Wallet connection was cancelled!');
}
}
};
```
**SCENARIO A2**: Still scenario A, but this is a shorter version:
```javascript
const { ethers, ethersProvider } = useContext(EthersContext);
// States
const [isConnected, setIsConnected] = useState(false);
const [userAccount, setUserAccount] = useState(null);
const [chainId, setChainId] = useState(null);
const [signer, setSigner] = useState(null);
const [provider, setProvider] = useState(null);
// Handlers
const onConnectHandler = async () => {
try {
const { provider, address, chainId, signer } = await ethersProvider.connect();
if (provider) setProvider(provider);
if (address) setUserAccount(address);
if (chainId) setChainId(chainId);
if (signer) setSigner(signer); // optional
setIsConnected(true);
} catch (e) {}
};
```
*The ethersProvider util inside scenario A2 will handle all initialization and errors.*
<br />
**SCENARIO B**: You have multiple components, and you want the Navigation component to handle the initialization and the rest of the other components can access the data, e.g., Body component can access all of it.
```javascript
import { useContext } from 'react';
import { EthersContext } from '../store/all-context-interface';
const Component = () => {
const { ethers, address, provider, chainId, signer, isConnected } = useContext(EthersContext);
// ...rest of the code
}
```
What is happening is that the ```<Navigation />``` component which contains the ```<Connector />``` component handles all connection and disconnection. So your ```<Body />``` component will become a receiver only and can still access all data through the EthersProvider context, based on the code above.
<br />
#### Scenario B - Sample Usage
```javascript
function App() {
return (
<EthersProvider askOnLoad={false}>
<Navigation />
<Body />
<FAQ />
</EthersProvider>
);
}
```
<br />
<br />
### MessageNet 🌴
---
The MessageNet is somehow also acts as a provider. Instead of importing the `<Message />` reusable component and use it for notifications, errors, etc., we will just encapsulate all components that need to spit out some UI messages. See example below:
```javascript
// HomePage
import { useContext } from 'react';
import { MsgNetContext } from '../store/all-context-interface';
const HomePage = () => {
const { setMsg } = useContext(MsgNetContext);
// Handlers
const mintHandler = async () => {
try {
// ... mint logic
setMsg('Nice, minted an NFT!', 'success');
} catch (e) {
setMsg('Something went wrong', 'warning');
}
}
// ...rest of the code
}
```
```javascript
// App
function App() {
return (
<MessageNet>
<EthersProvider askOnLoad={false}>
<HomePage />
</EthersProvider>
</MessageNet>
);
}
```
<br />
<br />
### Components Directory Structure 🚧
---
<p align="center" style="text-align: center">
<img src="https://i.imgur.com/gjkhJxW.png" alt="Image" width="30%" />
</p>
You can find these inside `src/components` directory. Though opinionated, I would highly suggest we do it like this. Just to stay a bit organized.
1. **`core`** --> Will contain all utility components, like the one that handles connections, processes data, etc.
2. **`pages`** --> Are simply for pages and layouts. If it's only a landing page, you can still call it `Home`. Then inside it you can put all layouts pertaining to `<Home />`.
3. **`ui`** --> Will contain all reusable UI components. Check next section.
<br />
<br />
<br />
<br />
### Custom Hooks Properties 🔥
---
**`useSignature`**
This custom hooks allows to quickly sign a message using a provider, for example, Metamask. You just pass a message you wanna sign and then gets all the data back. See example:
```javascript
const { sigData, signMessage } = useSignature();
// Will sign the message
const runSig = (msg) => signMessage(msg);
// Returns all data: signature, address, message
const logSig = () => console.log(sigData);
// address: "0x76fdd1d979eA8dd2a7561C14f763A3CdFd9648e7"
// message: "test message here"
// signature: "0xf9a278786520200b28eb6ed909242d0b9e26631e7cc3..."
```
The `signMessage` function takes 2 arguments, `message` and `signer`. The `message` is **required** but the `signer` is **optional** since this boilerplate is already encapsulated inside `EthersProvider` component.
<br />
<br />
### Reusable Components Properties 🔥
---
**`<Connector />`**
This core component is responsible for connecting and disconnecting the user. Below are the props and default props for this component:
```javascript
const Connector = ({
forceSwitch = true,
network = '0x1',
connText = 'CONNECT',
disconnText = 'DISCONNECT',
bgColor = '#5e356c',
hoverBgColor = '#2d996c',
lineColor = '#b3bdb3',
hoverLineColor = '#dae7da',
lineSize = 1,
curve = 23,
textSpace = 2,
textSize = 1.3,
font = 'Helvetica',
textColor = 'white',
textWeight = 700,
height = 12,
width = 210,
}) => {...}
```
`forceSwitch` is useful when you want to trigger network change after the *connecting* process. Especially, if askOnLoad is set to false in `<EthersProvider askOnLoad={false} />`. Default network or chain is '0x1' which is the Ethereum network.
<br />
**`<NiceButton />`**
This is a customizable and reusable button component. This can work as a **link**, a button with an **icon**, or just a **simple** button. Below are the props and default props for this component:
```javascript
const NiceButton = ({
curve = 7,
icon = null,
iconColor = 'white',
hoverIconColor = '#98ea9a',
link = null,
linkTarget = '_blank',
text = 'Pop Block',
lineColor = '#5e5e5e',
hoverLineColor = '#716e6e',
lineSize = 1,
bgColor = '#a276bf3b',
hoverBgColor = '#694A7D3B',
textColor = 'white',
textWeight = 600,
textSize = 1.3,
textSpace = 0,
font = 'Helvetica',
wordSpace = 3,
width = 15,
height = 48,
style = null,
method = null,
}) => {...}
```
Here is a sample usage of this component using it as a button with an icon:
```javascript
import { FaFingerprint } from 'react-icons/fa';
const Component = () => {
return (
<div className="...">
<NiceButton
text="mint"
method={onClickMintHandler}
textSize={1.3}
height={42}
width={20}
curve={13}
bgColor="#0C0D0C"
hoverBgColor="#0C0D0C"
icon={<FaFingerprint size={18} />}
/>
</div>
)
}
```
Recommended icon package to use is: https://react-icons.github.io/react-icons/. If the link props is present it will automatically be converted to link-type button.
<br />
**`<Crementor />`**
This reusable component is used when increment and decrement functions are needed. Below are the props and default props of this component:
```javascript
const Crementor = ({
value = 0,
width = 300,
height = 10,
curve = 50,
bgColor = '#2b3b5e',
lineColor = '#818181',
hoverLineColor = '#ac9595',
btnColor = '#dbdbdb',
hoverBtnColor = '#98ea9a',
onDecrement,
onIncrement,
}) => {...}
```
And, here is the sample usage of this component:
```javascript
const Component = () => {
const onIncrementHandler = () => {
setDefValue(defValue + 1);
};
const onDecrementHandler = () => {
if (defValue === 0) return;
setDefValue(defValue - 1);
};
return (
<div className="...">
<Crementor
value={defValue}
bgColor="#2b3b5e"
lineColor="#818181"
onIncrement={onIncrementHandler}
onDecrement={onDecrementHandler}
/>
</div>
)
}
```
<br />
<br />
**TODO - continue....**
<br />
<br />