├── .env ├── .gitignore ├── README.md ├── craco.config.js ├── dfx.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── run.sh ├── src ├── App.css ├── App.tsx ├── agent.ts ├── assets │ ├── dfn.svg │ └── logo.svg ├── index.css ├── index.tsx ├── react-app-env.d.ts ├── setupTests.ts ├── test │ └── main.mo ├── tests │ └── App.test.tsx └── types │ └── custom.d.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_CANISTER_ID=rwlgt-iiaaa-aaaaa-aaaaa-cai -------------------------------------------------------------------------------- /.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 | .dfx 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | /src/declarations/* 27 | /src/declarations/ 28 | 29 | # IDEs 30 | /.idea 31 | /.code 32 | 33 | yarn.lock 34 | 35 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/Naereen/StrapDown.js/graphs/commit-activity) 2 | 3 | build status 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 5 | 6 | 7 | 8 | 9 | 10 |
11 |

12 | 13 |

ReactJS Typescript Motoko Boilerplate + Authentication

14 |
15 | Node version: v16.18.1 16 | Updated for dfx 0.12.1 and @dfinity packages: 0.14.0 17 | 18 |

19 | 20 | ## About The Project 21 | Boilerplate ReactJS/Typescript with authentication to a local II 22 | 23 | ![screen-capture](https://user-images.githubusercontent.com/6564200/125112118-a9bc8d00-e0de-11eb-9b6b-c17bd9d18272.gif) 24 | 25 | ## Getting Started 26 | 27 | This is an example of how you may give instructions on setting up your project locally. 28 | To get a local copy up and running follow these simple example steps. 29 | 30 | ### Installation 31 | 1. Install Internet Identity locally from: https://github.com/dfinity/internet-identity 32 | 2. Clone the repo 33 | ```sh 34 | git clone https://github.com/gabrielnic/dfinity-react 35 | ``` 36 | 3. Install NPM packages 37 | ```sh 38 | yarn 39 | ``` 40 | 4. Start dfx 41 | ```sh 42 | dfx start 43 | ``` 44 | 5. Update `.env` with the II canister id eg: `Installing code for canister internet_identity, with the canister_id from internet-identity/.dfx/local/canister_ids.json -> local: eg: rwlgt-iiaaa-aaaaa-aaaaa-cai 45 | ` 46 | 47 | 6. Deploy 48 | ```sh 49 | dfx deploy 50 | ``` 51 | 7. Manual Deploy (replaces point 6) 52 | ``` 53 | dfx canister create --all 54 | dfx generate 55 | dfx build 56 | dfx canister install --all 57 | ``` 58 | 59 | ## Usage 60 | Copy front-end canister id from .dfx/local/canister_ids.json and replace in the url below 61 | 62 | 63 | Navigate to http://.localhost:8000/ 64 | 65 | 66 | 67 | ## Contributing 68 | 69 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 70 | 71 | 1. Fork the Project 72 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 73 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 74 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 75 | 5. Open a Pull Request 76 | 77 | 78 | 79 | ## License 80 | 81 | Distributed under the MIT License. See `LICENSE` for more information. 82 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | let localCanisters, prodCanisters, canisters; 5 | 6 | let localEnv = true; 7 | let network = 'local'; 8 | 9 | function initCanisterIds() { 10 | try { 11 | localCanisters = require(path.resolve(".dfx", "local", "canister_ids.json")); 12 | } catch (error) { 13 | console.log("No local canister_ids.json found. Continuing production"); 14 | } 15 | try { 16 | prodCanisters = require(path.resolve("canister_ids.json")); 17 | localEnv = false; 18 | } catch (error) { 19 | console.log("No production canister_ids.json found. Continuing with local"); 20 | } 21 | 22 | network = process.env.NODE_ENV === "production" && !localEnv ? "ic" : "local"; 23 | 24 | canisters = network === "local" || localEnv ? localCanisters : prodCanisters; 25 | for (const canister in canisters) { 26 | process.env[canister.toUpperCase() + "_CANISTER_ID"] = 27 | canisters[canister][network]; 28 | } 29 | }; 30 | 31 | const isDevelopment = process.env.NODE_ENV !== "production" || localEnv; 32 | 33 | initCanisterIds(); 34 | 35 | const asset_entry = path.join( 36 | "src", 37 | "assets", 38 | "src", 39 | "index.html" 40 | ); 41 | 42 | 43 | module.exports = { 44 | mode : "development", 45 | eslint: { 46 | enable: false, 47 | }, 48 | css: { 49 | loaderOptions: { /* Any css-loader configuration options: https://github.com/webpack-contrib/css-loader. */ }, 50 | loaderOptions: (cssLoaderOptions, { env, paths }) => { return cssLoaderOptions; } 51 | }, 52 | webpack: { 53 | alias: {}, 54 | plugins: [ 55 | new webpack.EnvironmentPlugin({ 56 | DFX_NETWORK: network, 57 | TEST_CANISTER_ID: canisters["test"], 58 | NODE_ENV: isDevelopment, 59 | }), 60 | ], 61 | configure: { /* Any webpack configuration options: https://webpack.js.org/configuration */ }, 62 | configure: (webpackConfig, { env, paths }) => { return webpackConfig; } 63 | }, 64 | devServer: { 65 | proxy: { 66 | "/api": { 67 | target: "http://localhost:4943", 68 | changeOrigin: true, 69 | pathRewrite: { 70 | "^/api": "/api", 71 | }, 72 | }, 73 | }, 74 | hot: true, 75 | }, 76 | plugins: { 77 | plugin: { 78 | overrideWebpackConfig: ({ webpackConfig, pluginOptions }) => { 79 | return { 80 | ...webpackConfig, 81 | mode: isDevelopment ? "development" : "production", 82 | entry: { 83 | index: path.join(__dirname, asset_entry).replace(/\.html$/, ".js"), 84 | }, 85 | devtool: isDevelopment ? "source-map" : false, 86 | optimization: { 87 | minimize: !isDevelopment, 88 | minimizer: [new TerserPlugin()], 89 | }, 90 | resolve: { 91 | extensions: [".js", ".ts", ".jsx", ".tsx"], 92 | fallback: { 93 | assert: require.resolve("assert/"), 94 | buffer: require.resolve("buffer/"), 95 | events: require.resolve("events/"), 96 | stream: require.resolve("stream-browserify/"), 97 | util: require.resolve("util/"), 98 | }, 99 | }, 100 | output: { 101 | filename: "index.js", 102 | path: path.join(__dirname, "build"), 103 | }, 104 | }; 105 | } 106 | }, 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "test": { 4 | "main": "src/test/main.mo", 5 | "type": "motoko" 6 | }, 7 | "frontend" : { 8 | "dependencies": ["test"], 9 | "frontend": { 10 | "entrypoint": "build/index.html" 11 | }, 12 | "source": [ 13 | "build" 14 | ], 15 | "type": "assets" 16 | } 17 | }, 18 | "defaults": { 19 | "build": { 20 | "output": "canisters", 21 | "packtool": "" 22 | } 23 | }, 24 | "version": 1 25 | } 26 | 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dfn-react-ts", 3 | "version": "0.1.3", 4 | "private": true, 5 | "homepage": ".", 6 | "dependencies": { 7 | "@dfinity/agent": "0.14.0", 8 | "@dfinity/auth-client": "0.14.0", 9 | "@dfinity/authentication": "0.14.0", 10 | "@dfinity/candid": "0.14.0", 11 | "@dfinity/identity": "0.14.0", 12 | "@dfinity/principal": "0.14.0", 13 | "@testing-library/jest-dom": "^5.11.4", 14 | "@testing-library/react": "^11.1.0", 15 | "@testing-library/user-event": "^12.1.10", 16 | "react": "^17.0.2", 17 | "react-dom": "^17.0.2", 18 | "react-scripts": "4.0.3", 19 | "ts-loader": "^8.0.18", 20 | "typescript": "^4.1.2", 21 | "web-vitals": "^1.0.1" 22 | }, 23 | "scripts": { 24 | "start": "PORT=3001 craco start --mode development --env development --network=local", 25 | "build": "craco build", 26 | "test": "craco test" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "@craco/craco": "^6.2.0", 48 | "@types/jest": "^26.0.15", 49 | "@types/node": "^12.0.0", 50 | "@types/react": "^17.0.0", 51 | "@types/react-dom": "^17.0.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielnic/dfinity-react/5db7990c84a730bdb7b8567ba0c36ed7b0ee06aa/public/favicon.ico -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielnic/dfinity-react/5db7990c84a730bdb7b8567ba0c36ed7b0ee06aa/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrielnic/dfinity-react/5db7990c84a730bdb7b8567ba0c36ed7b0ee06aa/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | # echo PATH = $PATH 2 | # echo vessel @ `which vessel` 3 | 4 | echo 5 | echo == Create. 6 | echo 7 | 8 | dfx canister create --all 9 | 10 | 11 | echo 12 | echo == Generate. 13 | echo 14 | 15 | dfx generate test 16 | 17 | echo 18 | echo == Build. 19 | echo 20 | 21 | dfx build test 22 | 23 | echo 24 | echo == Install. 25 | echo 26 | 27 | dfx canister install test --mode=reinstall 28 | 29 | dfx canister call test generateRandom 30 | echo 31 | echo == Deploy. 32 | echo 33 | 34 | # yarn start 35 | # dfx deploy -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | 40 | .login-logo { 41 | margin: 0 auto; 42 | text-align: center; 43 | border: none; 44 | background: none; 45 | border: none !important; 46 | cursor: grab; 47 | background: none; 48 | border-radius: 0; 49 | outline: 0 !important; 50 | } -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useCallback, MouseEventHandler } from 'react'; 2 | import dfn from './assets/dfn.svg'; 3 | import './App.css'; 4 | 5 | import { BackendActor } from './agent'; 6 | import { AuthClient } from "@dfinity/auth-client"; 7 | 8 | 9 | function App() { 10 | 11 | const [val, setVal] = useState(0); 12 | const [logged, setLogged] = useState(false); 13 | const [identity, setIdentity] = useState(''); 14 | 15 | useEffect(() => { 16 | getValue(); 17 | isAuth(); 18 | }, [identity]); 19 | 20 | const onIncrement = useCallback(async () => { 21 | const ba = await BackendActor.getBackendActor(); 22 | const value = await ba.increment(); 23 | setVal(Number(value)); 24 | 25 | }, []); 26 | 27 | const isAuth = async () => { 28 | const authClient = await AuthClient.create(); 29 | const isAuth = await authClient.isAuthenticated(); 30 | if (isAuth) { 31 | setLogged(true); 32 | } 33 | } 34 | 35 | const whoami = async() => { 36 | const ba = await BackendActor.getBackendActor(); 37 | const value = await ba.whoami(); 38 | setIdentity(value.toText()); 39 | } 40 | 41 | const getValue = async() => { 42 | if (logged) { 43 | whoami(); 44 | } 45 | const ba = await BackendActor.getBackendActor(); 46 | const val = await ba.getValue(); 47 | setVal(Number(val)); 48 | } 49 | 50 | const handleLogin = async (e: React.FormEvent) => { 51 | e.preventDefault(); 52 | const daysToAdd = 7; 53 | const expiry = Date.now() + (daysToAdd * 86400000); 54 | const authClient = await AuthClient.create(); 55 | await authClient.login({ 56 | onSuccess: async () => { 57 | BackendActor.setAuthClient(authClient); 58 | const ba = await BackendActor.getBackendActor(); 59 | const principal = await ba.whoami(); 60 | setIdentity(principal.toText()); 61 | // eslint-disable-next-line no-restricted-globals 62 | location.reload(); 63 | }, 64 | identityProvider: "http://localhost:4943?canisterId=" + process.env.REACT_APP_CANISTER_ID, 65 | maxTimeToLive: BigInt(expiry * 1000000) 66 | }); 67 | } 68 | 69 | 70 | return ( 71 |
72 |
73 |

Value received from Dfinity: {val}

74 | {!logged && 75 |
76 |
In order to increase the counter you need to be logged in
77 |

Log in with

78 | 83 |
84 | } 85 | {logged && 86 |
87 |
Hi {identity}, you're logged in now!
88 | 89 |
90 | 91 | } 92 |
93 |
94 | ); 95 | } 96 | 97 | export default App; 98 | -------------------------------------------------------------------------------- /src/agent.ts: -------------------------------------------------------------------------------- 1 | import { Actor, HttpAgent, ActorSubclass } from '@dfinity/agent'; 2 | import { AuthClient } from "@dfinity/auth-client"; 3 | import { _SERVICE } from "./declarations/test/test.did"; 4 | import { createActor, canisterId } from "./declarations/test"; 5 | 6 | export namespace BackendActor { 7 | var authClient: AuthClient; 8 | export async function setAuthClient(ac: AuthClient) { 9 | authClient = ac; 10 | } 11 | export async function getBackendActor(): Promise> { 12 | if (!authClient) { 13 | authClient = await AuthClient.create(); 14 | } 15 | const identity = authClient.getIdentity(); 16 | const backendActor = createActor(canisterId as string, { 17 | agentOptions: { 18 | identity, 19 | } 20 | }); 21 | 22 | return backendActor; 23 | } 24 | }; -------------------------------------------------------------------------------- /src/assets/dfn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 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/test/main.mo: -------------------------------------------------------------------------------- 1 | import Principal "mo:base/Principal"; 2 | import Random "mo:base/Random"; 3 | import Hash "mo:base/Hash"; 4 | import Nat8 "mo:base/Nat8"; 5 | import Text "mo:base/Text"; 6 | import Char "mo:base/Char"; 7 | import Nat32 "mo:base/Nat32"; 8 | import Nat "mo:base/Nat"; 9 | import Debug "mo:base/Debug"; 10 | import Prim "mo:prim"; 11 | 12 | shared ({caller = owner}) actor class Test() = this { 13 | stable var currentValue: Nat = 1; 14 | 15 | public shared (msg) func increment(): async Nat { 16 | currentValue += 1; 17 | 18 | currentValue 19 | }; 20 | 21 | public query func getValue(): async Nat { 22 | currentValue; 23 | }; 24 | 25 | public func whoami() : async Principal { 26 | return Principal.fromActor(this); 27 | }; 28 | 29 | } -------------------------------------------------------------------------------- /src/tests/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from '../App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/types/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg"; 2 | 3 | // For now, canisters are untyped 4 | declare module "dfx-generated/*"; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-jsx", 4 | "target": "es5", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true 22 | }, 23 | "include": [ 24 | "src", 25 | "./types/**/*" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------