├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── images │ ├── exit.png │ ├── enter.png │ ├── 3DK_LOGO.png │ └── 3DK_LOGO_ICON_1300.png ├── GlobalState │ ├── reducers.js │ ├── Store.js │ └── UserReducer.js ├── pages │ ├── LandingPage.jsx │ ├── Home.jsx │ └── Page2.jsx ├── setupTests.js ├── reportWebVitals.js ├── index.js ├── App.js ├── ProtectedRoute.jsx ├── App.scss ├── components │ └── Menu.jsx └── UserService.js ├── .gitignore ├── package.json └── README.md /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dkrender/wax_react_template/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dkrender/wax_react_template/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dkrender/wax_react_template/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/images/exit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dkrender/wax_react_template/HEAD/src/images/exit.png -------------------------------------------------------------------------------- /src/images/enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dkrender/wax_react_template/HEAD/src/images/enter.png -------------------------------------------------------------------------------- /src/images/3DK_LOGO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dkrender/wax_react_template/HEAD/src/images/3DK_LOGO.png -------------------------------------------------------------------------------- /src/images/3DK_LOGO_ICON_1300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3dkrender/wax_react_template/HEAD/src/images/3DK_LOGO_ICON_1300.png -------------------------------------------------------------------------------- /src/GlobalState/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import user from './UserReducer'; 3 | export const rootReducer = combineReducers({ 4 | user 5 | }); 6 | -------------------------------------------------------------------------------- /src/pages/LandingPage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const LandingPage = () => { 4 | return (<> 5 |

Welcome!

6 |

Landing Page

7 | ); 8 | } -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/GlobalState/Store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import { rootReducer as reducer } from './reducers'; 3 | 4 | const Store = configureStore({ 5 | reducer, 6 | devTools: true 7 | }); 8 | 9 | export const storeAppDispatch = Store.dispatch; 10 | export default Store; 11 | -------------------------------------------------------------------------------- /src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Home = () => { 4 | return ( 5 |
6 |
7 |
8 |

Welcome to Home

9 |
10 |
11 |
12 | ); 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/GlobalState/UserReducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | name: '', 5 | isLogged: false, 6 | balance: 0 7 | }; 8 | 9 | const user = createSlice({ 10 | name: 'user', 11 | initialState, 12 | reducers: { 13 | setPlayerData: (state, action) => (action.payload), 14 | setPlayerLogout: (state, action) => initialState, 15 | setPlayerBalance: (state, action) => ({...state, balance: action.payload}) 16 | } 17 | }); 18 | 19 | export const { setPlayerData, setPlayerLogout, setPlayerBalance } = user.actions; 20 | export default user.reducer; 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import App from './App'; 5 | import Store from './GlobalState/Store'; 6 | import reportWebVitals from './reportWebVitals'; 7 | import { UserService } from './UserService'; 8 | 9 | UserService.init(); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ); 19 | 20 | // If you want to start measuring performance in your app, pass a function 21 | // to log results (for example: reportWebVitals(console.log)) 22 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 23 | reportWebVitals(); 24 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import './App.scss'; 2 | import { Menu } from './components/Menu'; 3 | import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'; 4 | import { Home } from './pages/Home'; 5 | import { Page2 } from './pages/Page2'; 6 | import ProtectedRoute from './ProtectedRoute'; 7 | import { LandingPage } from './pages/LandingPage'; 8 | 9 | function App() { 10 | return ( 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/ProtectedRoute.jsx: -------------------------------------------------------------------------------- 1 | import { Redirect, Route } from 'react-router-dom'; 2 | import React from 'react'; 3 | import { UserService } from './UserService'; 4 | 5 | const ProtectedRoute = ({ component: Component, ...rest }) => { 6 | return ( 7 | { 9 | if (UserService.isLogged()) { 10 | return 11 | } else { 12 | return 20 | } 21 | } 22 | } /> 23 | ) 24 | } 25 | 26 | export default ProtectedRoute; -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import "~bootstrap/scss/bootstrap.scss"; 2 | 3 | body { 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 6 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 7 | sans-serif; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | 12 | .App { 13 | background-color: #222f3e; 14 | width: 100%; 15 | height: 100vh; 16 | background-image: url("./images/3DK_LOGO.png"); 17 | background-position: center; 18 | background-repeat: no-repeat; 19 | } 20 | 21 | $heightMenu: 50px; 22 | #menu { 23 | height: $heightMenu; 24 | background-color: #11171f; 25 | .btn-item { 26 | background-color: #11171f; 27 | text-align: center; 28 | width: 100px; 29 | height: $heightMenu; 30 | line-height: $heightMenu; 31 | border-right: 1px solid #ff9f43; 32 | border-left: 1px solid #ff9f43; 33 | border-top: none; 34 | border-bottom: none; 35 | text-decoration: none; 36 | color: #ff9f43; 37 | margin-right: -1px; 38 | &:hover { 39 | background-color: #1e2835; 40 | color: #feca57; 41 | } 42 | } 43 | .disable{ 44 | opacity: 0.5; 45 | } 46 | } 47 | 48 | .ual-button-gen { 49 | display: none; 50 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ual_template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@eosdacio/ual-wax": "^1.4.4", 7 | "@reduxjs/toolkit": "^1.6.1", 8 | "@testing-library/jest-dom": "^5.14.1", 9 | "@testing-library/react": "^11.2.7", 10 | "@testing-library/user-event": "^12.8.3", 11 | "anchor-link": "^3.5.1", 12 | "bootstrap": "^5.2.0", 13 | "lodash": "^4.17.21", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-redux": "^7.2.4", 17 | "react-router-dom": "^5.2.0", 18 | "react-scripts": "^5.0.1", 19 | "ual-anchor": "^1.3.0", 20 | "ual-plainjs-renderer": "^0.3.0", 21 | "web-vitals": "^2.1.2" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | }, 47 | "devDependencies": { 48 | "node-sass": "^7.0.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/Page2.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { UserService } from '../UserService'; 3 | 4 | export const Page2 = () => { 5 | 6 | const onHandleSendWax = () => { 7 | UserService.session.signTransaction( 8 | { 9 | actions: [{ 10 | account: 'eosio.token', 11 | name: 'transfer', 12 | authorization: [{ 13 | actor: UserService.authName, 14 | permission: 'active' 15 | }], 16 | data: { 17 | from: UserService.authName, 18 | to: '3dkrenderwax', 19 | quantity: '1.00000000 WAX', 20 | memo: 'This works!' 21 | } 22 | }] 23 | }, 24 | { 25 | blocksBehind: 3, 26 | expireSeconds: 30 27 | } 28 | ).then((response) => { 29 | if(response.status === 'executed') { 30 | UserService.getBalance(); 31 | } 32 | }); 33 | 34 | // Refund balance 35 | } 36 | 37 | return ( 38 |
39 |
40 |
41 |

Welcome to Page2

42 |
43 |
44 | 45 |
46 |
47 |
48 | ); 49 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/Menu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import { Link, Redirect } from 'react-router-dom'; 4 | import LogoIcon from '../images/3DK_LOGO_ICON_1300.png'; 5 | import EnterIcon from '../images/enter.png'; 6 | import ExitIcon from '../images/exit.png'; 7 | import { useHistory } from 'react-router-dom'; 8 | import { UserService } from '../UserService'; 9 | import { useDispatch } from 'react-redux'; 10 | import { setPlayerLogout } from '../GlobalState/UserReducer'; 11 | 12 | export const Menu = (props) => { 13 | 14 | const dispatch = useDispatch(); 15 | const locationHistory = useHistory(); 16 | const UserState = useSelector((store) => store.user); 17 | 18 | const handleLogin = () => { 19 | UserService.login(() => { 20 | if (UserService.isLogged()) { 21 | locationHistory.push('/home'); 22 | } else { 23 | dispatch(setPlayerLogout()); 24 | } 25 | }); 26 | } 27 | 28 | const onHandleLogout = () => { 29 | UserService.logout(); 30 | } 31 | 32 | return ( 33 | 60 | ); 61 | } -------------------------------------------------------------------------------- /src/UserService.js: -------------------------------------------------------------------------------- 1 | import { UALJs } from 'ual-plainjs-renderer'; 2 | import { Wax } from '@eosdacio/ual-wax'; 3 | import { isEmpty } from 'lodash'; 4 | import { Anchor } from 'ual-anchor'; 5 | 6 | import {storeAppDispatch} from './GlobalState/Store'; 7 | import { setPlayerBalance, setPlayerData, setPlayerLogout } from './GlobalState/UserReducer'; 8 | 9 | /** 10 | * Class to manage user data; it will be saved on Login and deleted on Logout 11 | */ 12 | export class User { 13 | 14 | appName = 'ual_template'; 15 | 16 | /** 17 | * WAX Mainnet configuration 18 | */ 19 | myChain = { 20 | chainId: '1064487b3cd1a897ce03ae5b6a865651747e2e152090f99c1d19d44e01aea5a4', 21 | rpcEndpoints: [{ 22 | protocol: 'https', 23 | host: 'apiwax.3dkrender.com', 24 | port: '' 25 | }] 26 | }; 27 | 28 | /** 29 | * WAX Testnet configuration 30 | */ 31 | // myChain = { 32 | // chainId: 'f16b1833c747c43682f4386fca9cbb327929334a762755ebec17f6f23c9b8a12', 33 | // rpcEndpoints: [{ 34 | // protocol: 'https', 35 | // host: 'testnet-wax.3dkrender.com', 36 | // port: '' 37 | // }] 38 | // }; 39 | 40 | ual; 41 | 42 | // User session data 43 | authName = undefined; 44 | serviceLoginName = undefined; 45 | // Shows petition signing and current balance obtaining methods 46 | session = undefined; 47 | 48 | // Current balance 49 | userBalance = 0; 50 | 51 | // Callback to refer to successful login 52 | callbackServerUserData = undefined; 53 | 54 | getName() { 55 | return this.authName; 56 | } 57 | 58 | login(callback) { 59 | const ualButton = document.querySelector(".ual-button-gen"); 60 | ualButton.click(); 61 | 62 | this.callbackServerUserData = callback; 63 | } 64 | 65 | isLogged() { 66 | const auth = !isEmpty(this.authName) && !isEmpty(this.session); 67 | return auth; 68 | } 69 | 70 | logout() { 71 | console.log("Logout"); 72 | this.authName = undefined; 73 | this.session = undefined; 74 | 75 | this.ual.logoutUser(); 76 | 77 | storeAppDispatch(setPlayerLogout()); 78 | 79 | if(this.callbackServerUserData !== undefined) { 80 | this.callbackServerUserData(); 81 | } 82 | } 83 | 84 | // UAL API call response 85 | async ualCallback(userObject) { 86 | 87 | this.session = userObject[0]; 88 | this.serviceLoginName = this.session.constructor.name; 89 | this.authName = this.session.accountName; 90 | 91 | storeAppDispatch(setPlayerData({ 92 | name: this.authName, 93 | isLogged: this.isLogged(), 94 | balance: (this.balance !== undefined) ? this.balance : 0 95 | })); 96 | 97 | this.getBalance(); 98 | 99 | if(this.callbackServerUserData !== undefined) { 100 | this.callbackServerUserData(); 101 | } 102 | } 103 | 104 | getBalance() { 105 | const balance = this.session.rpc.get_account(this.authName); 106 | balance.then((balance) => { 107 | this.balance = balance.core_liquid_balance; 108 | storeAppDispatch(setPlayerBalance((this.balance !== undefined) ? this.balance : 0)); 109 | }); 110 | return balance; 111 | } 112 | 113 | // UserService init called to prepare UAL Login. 114 | init() { 115 | // Binding: 116 | this.ualCallback = this.ualCallback.bind(this); 117 | 118 | const wax = new Wax([this.myChain], { appName: this.appName }); 119 | 120 | const anchor = new Anchor([this.myChain], { appName: this.appName }); 121 | 122 | const divUal = document.createElement('div') 123 | divUal.setAttribute('id', 'ual-login'); 124 | document.body.appendChild(divUal); 125 | 126 | const divLoginRoot = document.getElementById('ual-login'); 127 | this.ual = new UALJs(this.ualCallback, [this.myChain], this.appName, [wax, anchor], { containerElement: divLoginRoot }); 128 | this.ual.init() 129 | } 130 | 131 | static new() { 132 | if (!User.instance) { 133 | User.instance = new User(); 134 | } 135 | 136 | return User.instance; 137 | } 138 | } 139 | 140 | export const UserService = User.new(); 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Template for WAX (UAL) 2 | 3 | This template was created to make the use of WAX with React easier. 4 | 5 | ![Logo 3DKRender](https://cdn.discordapp.com/attachments/813862875944845313/813866667150409769/3DK_LOGO_400x120.png) 6 | 7 | Please remember to install all needed dependencies beforehand: 8 | ``` 9 | > npm i 10 | ``` 11 | You can start the project by: 12 | ``` 13 | > npm run start 14 | ``` 15 | 16 | ## Dependencies used 17 | - Dependencies for WAX 18 | - @eosdacio/ual-wax 19 | - ual-anchor 20 | - ual-plainjs-renderer 21 | - anchor-link 22 | - Dependencies for React routes 23 | - react-router-dom 24 | 25 | - Global state dependencies 26 | - redux 27 | - react-redux 28 | - @reduxjs/toolkit 29 | 30 | - Style and design dependencies 31 | - bootstrap 32 | - glamor 33 | 34 | - Help dependencies 35 | - lodash 36 | 37 | ## Key files 38 | 39 | - **UserService.js**: A file needed to manage the user's login, UAL configuration and different login methods. It also has a certain mode that lets us know the user's status (**isLogged()**) with the answers **true** or **false** if the user has previously logged in. 40 | 41 | **Remember** to comment and uncomment as needed, depending on wether you will be working with mainnet or testnet. 42 | 43 | When the user logs in, this data will be saved in the app's global state thanks to Redux and @reduxjs/toolkit (we will also save an **isLogged** in order to maintain a live actualization in React). 44 | 45 | - **GlobalState/**: In this folder we will keep our configuration and **store** from **redux**, so we can save and manage the user's data in a global state. 46 | 47 | - **App.js**: With this file we can manage the web's route system. 48 | 49 | - **index.js**: This file initiates **UserService** (UserService.init()) and also contains the for **store** from **redux**. 50 | 51 | - **pages**: In this folder we will save our web's pages. **Remember to configure new pages' routes in your App.js**. 52 | 53 | - **components**: In this folder we will keep our app's components. For this example we only have **Menu.jsx**, a component for our menu, which will help us redirect the user when they log in. 54 | 55 | ## File Menu.jsx 56 | The file **components/Menu.jsx** is a component from our app or web's menu, which has four tabs: Main, Home, Page2, Login/Logout. 57 | 58 | As we can see, we have two tabs which are disabled and we are not allowed to access them. These are: **Home** and **Page2**. To achieve this, we will simply check out if the user has logged in or not, thanks to **UserState.isLogged** (in the redux state), and we will show or lock them with any **CSS** style. 59 | 60 | As for the Login/Logout tab, we will show one or the other depending on the user's state. This is easily ckeched by opening the file, but you will probably see something like this: 61 | ```jsx 62 | { 63 | !UserState.isLogged && 64 | 65 | } 66 | { 67 | UserState.isLogged && 68 | Logout 69 | } 70 | ``` 71 | ## Login system 72 | 73 | The system to start login is located in **components/Menu.jsx**. Once we click on the login button in the menu, this will trigger **handleLogin** and at the same time will activate the **UserService.login()** fuction. In this function it is possible to add an anonymous function as a callback, and the answer will be delivered once we log in. This callback will allow us to check if the user has logged in, if they have. 74 | If that is the case, we will be redirected to a page (**/home, in this case**). Otherwise, it will log out in order to clear data. 75 | 76 | ```jsx 77 | const handleLogin = () => { 78 | UserService.login(() => { 79 | if (UserService.isLogged()) { 80 | locationHistory.push('/home'); 81 | } else { 82 | dispatch(setPlayerLogout()); 83 | } 84 | }); 85 | } 86 | ``` 87 | 88 | ## React routes' protection 89 | 90 | Routes must be protected. For that, we will need to create a component called **ProtectedRouter.jsx**. We have created one for this example, and its function will be to ckeck the route we're in and to show us the user's state so we can know if they have logged in or not (**UserService.isLogged()**). If the user has not logged in, it will take the user from this route and redirect them to another one we have configured previously, which in this case leads to **/login**. 91 | 92 | In order to use this component, we will simply open our **App.js**, where we will substitute for our . You will see this if you open **App.js**: 93 | 94 | ```jsx 95 | function App() { 96 | return ( 97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
108 | ); 109 | } 110 | ``` 111 | 112 | ## Send WAX 113 | 114 | You can see an example of how to send items from WAX in **pages/Page2.jsx**. The **UserService** saves the session and we simply have to access this session and sing any transaction. The code you will see in this example looks like this: 115 | 116 | ```js 117 | UserService.session.signTransaction( 118 | { 119 | actions: [{ 120 | account: 'eosio.token', 121 | name: 'transfer', 122 | authorization: [{ 123 | actor: UserService.authName, 124 | permission: 'active' 125 | }], 126 | data: { 127 | from: UserService.authName, 128 | to: '3dkrenderwax', 129 | quantity: '1.00000000 WAX', 130 | memo: 'This works!' 131 | } 132 | }] 133 | }, 134 | { 135 | blocksBehind: 3, 136 | expireSeconds: 30 137 | } 138 | 139 | ).then((response) => { 140 | if(response.status === 'executed') { 141 | UserService.getBalance(); 142 | } 143 | }); 144 | ``` 145 | --- 146 | ![Logo 3DKRender](https://cdn.discordapp.com/attachments/813862875944845313/813866667150409769/3DK_LOGO_400x120.png) 147 | 148 | We hope that you find our tools useful and that you can keep developing your ideas with this awesome WAX technology. 149 | 150 | --- 151 |
152 |
153 | 154 | # [ES] - React Template for WAX (UAL) 155 | 156 | Template creado para facilitar el uso de WAX con React. 157 | 158 | ![Logo 3DKRender](https://cdn.discordapp.com/attachments/813862875944845313/813866667150409769/3DK_LOGO_400x120.png) 159 | 160 | Recuerda que primero es necesario instalar las dependencias: 161 | ``` 162 | > npm i 163 | ``` 164 | Para iniciar el proyecto se puede hacer con: 165 | ``` 166 | > npm run start 167 | ``` 168 | 169 | ## Dependencias usadas 170 | - Dependencias para el uso de WAX 171 | - @eosdacio/ual-wax 172 | - ual-anchor 173 | - ual-plainjs-renderer 174 | - anchor-link 175 | - Dependencia de rutas de react 176 | - react-router-dom 177 | 178 | - Dependencias de estado global 179 | - redux 180 | - react-redux 181 | - @reduxjs/toolkit 182 | 183 | - Dependencias de diseño y estilos 184 | - bootstrap 185 | - glamor 186 | 187 | - Dependencias de ayuda 188 | - lodash 189 | 190 | ## Archivos importantes 191 | 192 | - **UserService.js**: Archivo necesario para gestionar el login del usuario y la configuración de UAL y distintos sistemas de login, además de que cuenta con un método que nos indica el estado del usuario (**isLogged()**) devolviendo un **true** o **false** si el usuario ya ha hecho login. 193 | 194 | **Recuerda** comentar o descomentar las líneas según te convenga para trabajar con mainnet o con testnet. 195 | 196 | Cuando el usuario haya hecho log-in, estos datos se guardarán en el estado global de la aplicación gracias a Redux y @reduxjs/toolkit (también guardamos un **isLogged** para tener una actualización en tiempo real en React). 197 | 198 | - Carpeta **GlobalState/**: En esta carpeta tenemos la configuración y el **store** de **redux** para poder guardar y gestionar los datos del usuario en un estado global. 199 | 200 | - Archivo **App.js**: En este archivo gestionamos el sistema de rutas de la página. 201 | 202 | - Archivo **index.js**: En este archivo iniciamos el **UserService** (UserService.init()) y también tenemos el del **store** de **redux**. 203 | 204 | - Carpeta **pages**: Dentro de esta carpeta guardaremos las páginas de nuestro sitio. **Recuerda configurar las rutas de las páginas nuevas en tu App.js**. 205 | 206 | - Carpeta **components**: Aquí se almacenarán los componentes de nuestra aplicación. Para este ejemplo solo tenemos **Menu.jsx**, que es un componente del menú y que nos ayuda a redireccionar al usuario cuando este haga log-in. 207 | 208 | ## Archivo Menu.jsx 209 | El archivo **components/Menu.jsx** es el componente del menú de nuestra aplicación/página y cuenta con cuatro pestañas: Main, Home, Page2 y Login/Logout. 210 | 211 | Si nos fijamos, veremos que tenemos dos pestañas deshabilitadas a las que no se nos permite el acceso: **Home** y **Page2**. Para lograr hacer esto, simplemente deberemos comprobar si el usuario ha hecho log-in o no, gracias a **UserState.isLogged** (en el estado de redux), y se mostrará u ocultará con algún estilo **CSS**. 212 | 213 | En cuanto a la pestaña Login/Logout, mostraremos una u otra dependiendo del estado del usuario. Esto se puede comprobar fácilmente al entrar en el archivo, pero lo que veremos será algo así: 214 | ```jsx 215 | { 216 | !UserState.isLogged && 217 | 218 | } 219 | { 220 | UserState.isLogged && 221 | Logout 222 | } 223 | ``` 224 | ## Sistema de login 225 | 226 | El sistema de inicio de log-in está en el **components/Menu.jsx**. Una vez que se haga click sobre el botón de log-in en el menú, este llama a **handleLogin** y a la vez llama a la función de **UserService.login()**. Dentro de esta función se puede pasar una función anónima como callback, y cuando se haya hecho log-in, se recibirá una respuesta. Dentro de este callback comprobaremos si se ha hecho log-in o no. 227 | Si se hace log-in se redirigirá hacia una página (**en este caso, /home**). De lo contrario, se hará log-out del usuario para limpiar datos. 228 | 229 | ```jsx 230 | const handleLogin = () => { 231 | UserService.login(() => { 232 | if (UserService.isLogged()) { 233 | locationHistory.push('/home'); 234 | } else { 235 | dispatch(setPlayerLogout()); 236 | UserService.logout(); 237 | } 238 | }); 239 | } 240 | ``` 241 | 242 | ## Protección de las rutas de React 243 | 244 | Las rutas deben protegerse, por lo que es necesario crear un componente llamado **ProtectedRouter.jsx**. Nosotros hemos creado uno para el ejemplo, el cual comprueba la ruta en la que estamos y obtiene el estado del usuario para saber si ha hecho log-in o no (**UserService.isLogged()**). Si no se ha hecho log-in, sacará al usuario de esa ruta y lo mandará hacia otra ruta que hemos preconfigurado, en este caso, hacia **/login**. 245 | 246 | Para usar el componente, simplemente iremos a nuestro **App.js** y ahí reemplazaremos el por nuestro . Si abrimos **App.js**, lo veremos fácilmente: 247 | 248 | ```jsx 249 | function App() { 250 | return ( 251 |
252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 |
262 | ); 263 | } 264 | ``` 265 | 266 | ## Enviar WAX 267 | 268 | Para ver un ejemplo de cómo enviar algo de WAX, puedes ir a **pages/Page2.jsx**. El **UserService** guarda la sesión, y, si simplemente accedemos a esa sesión y firmamos alguna transacción, el código que veremos de ejemplo será el siguiente: 269 | 270 | ```js 271 | UserService.session.signTransaction( 272 | { 273 | actions: [{ 274 | account: 'eosio.token', 275 | name: 'transfer', 276 | authorization: [{ 277 | actor: UserService.authName, 278 | permission: 'active' 279 | }], 280 | data: { 281 | from: UserService.authName, 282 | to: '3dkrenderwax', 283 | quantity: '1.00000000 WAX', 284 | memo: 'This works!' 285 | } 286 | }] 287 | }, 288 | { 289 | blocksBehind: 3, 290 | expireSeconds: 30 291 | } 292 | 293 | ).then((response) => { 294 | if(response.status === 'executed') { 295 | UserService.getBalance(); 296 | } 297 | }); 298 | ``` 299 | --- 300 | ![Logo 3DKRender](https://cdn.discordapp.com/attachments/813862875944845313/813866667150409769/3DK_LOGO_400x120.png) 301 | 302 | Esperamos que nuestras herramientas te faciliten la vida y que puedas seguir desarrollando en esta increíble tecnología WAX. --------------------------------------------------------------------------------