| @@ -0,0 +1,19 @@ | ||
| { | ||
| "name": "app", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "dependencies": { | ||
| "axios": "^0.18.0", | ||
| "bootstrap": "^4.1.0", | ||
| "react": "^16.3.2", | ||
| "react-dom": "^16.3.2", | ||
| "react-scripts": "1.1.4", | ||
| "reactstrap": "^5.0.0-beta.3" | ||
| }, | ||
| "scripts": { | ||
| "start": "react-scripts start", | ||
| "build": "react-scripts build", | ||
| "test": "react-scripts test --env=jsdom", | ||
| "eject": "react-scripts eject" | ||
| } | ||
| } |
| @@ -0,0 +1,40 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
| <meta name="theme-color" content="#000000"> | ||
| <!-- | ||
| manifest.json provides metadata used when your web app is added to the | ||
| homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ | ||
| --> | ||
| <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> | ||
| <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> | ||
| <!-- | ||
| Notice the use of %PUBLIC_URL% in the tags above. | ||
| It will be replaced with the URL of the `public` folder during the build. | ||
| Only files inside the `public` folder can be referenced from the HTML. | ||
| Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will | ||
| work correctly both with client-side routing and a non-root public URL. | ||
| Learn how to configure a non-root public URL by running `npm run build`. | ||
| --> | ||
| <title>SMS Guinea</title> | ||
| </head> | ||
| <body> | ||
| <noscript> | ||
| You need to enable JavaScript to run this app. | ||
| </noscript> | ||
| <div id="root"></div> | ||
| <!-- | ||
| This HTML file is a template. | ||
| If you open it directly in the browser, you will see an empty page. | ||
| You can add webfonts, meta tags, or analytics to this file. | ||
| The build step will place the bundled scripts into the <body> tag. | ||
| To begin the development, run `npm start` or `yarn start`. | ||
| To create a production bundle, use `npm run build` or `yarn build`. | ||
| --> | ||
| </body> | ||
| </html> |
| @@ -0,0 +1,15 @@ | ||
| { | ||
| "short_name": "React App", | ||
| "name": "Create React App Sample", | ||
| "icons": [ | ||
| { | ||
| "src": "favicon.ico", | ||
| "sizes": "64x64 32x32 24x24 16x16", | ||
| "type": "image/x-icon" | ||
| } | ||
| ], | ||
| "start_url": "./index.html", | ||
| "display": "standalone", | ||
| "theme_color": "#000000", | ||
| "background_color": "#ffffff" | ||
| } |
| @@ -0,0 +1,48 @@ | ||
| .App-logo { | ||
| height: 200px; | ||
| margin-bottom: 1em; | ||
| } | ||
|
|
||
| .App-title { | ||
| font-size: 1.5em; | ||
| } | ||
|
|
||
| .App-header { | ||
| background-color: #AFD7B4; | ||
| padding: 2em; | ||
| color: #3b3b3b; | ||
| text-align: center; | ||
| } | ||
|
|
||
| .App-header p { | ||
| font-size: 1.25em; | ||
| font-weight: 500; | ||
| } | ||
|
|
||
| .body-content { | ||
| width: 50vw; | ||
| max-width: 800px; | ||
| margin: 2em auto 0; | ||
| } | ||
|
|
||
| .convo { | ||
| font-weight: 500; | ||
| margin-bottom: 2em; | ||
| } | ||
|
|
||
| .form-group label { | ||
| font-size: .9em; | ||
| margin-bottom: 0; | ||
| color: #868686; | ||
| } | ||
|
|
||
| button.btn-info { | ||
| background-color: #AFD7B4; | ||
| border: none; | ||
| color: rgb(74, 94, 76); | ||
| font-weight: 500; | ||
| } | ||
|
|
||
| button.btn-info:hover { | ||
| background-color: #148dcd; | ||
| } |
| @@ -0,0 +1,137 @@ | ||
| import React, { Component } from 'react'; | ||
| import axios from 'axios'; | ||
| import { Button, FormGroup, Label, Input } from 'reactstrap'; | ||
| import hello from './images/guinea-hello.png'; | ||
| import almost from './images/guinea-almost.png'; | ||
| import welcome from './images/guinea-welcome.png'; | ||
| import './App.css'; | ||
|
|
||
| axios.defaults.baseURL = 'http://localhost:4000'; | ||
|
|
||
| const Guinea = ({ requestId, verified }) => { | ||
| if (! requestId) { | ||
| return <img src={hello} className="App-logo" alt="hello guinea" />; | ||
| } | ||
| if (requestId && ! verified) { | ||
| return <img src={almost} className="App-logo" alt="almost guinea" /> | ||
| } | ||
| if (requestId && verified) { | ||
| return <img src={welcome} className="App-logo" alt="welcome guinea" /> | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| const Request = ({ disabled, onChange, onClick, requestId }) => { | ||
| if (requestId) { | ||
| return null; | ||
| } | ||
| return ( | ||
| <div className="body-content"> | ||
| <p className="convo">But first, I need to know it's really you.</p> | ||
| <p>Enter your phone number below and let's see...</p> | ||
| <FormGroup> | ||
| <Label for="phone">Phone Number</Label> | ||
| <Input type="tel" name="phone" id="phone" placeholder="e.g.: 447700900000" onChange={(event) => onChange(event)} /> | ||
| </FormGroup> | ||
| <Button color="info" onClick={() => onClick()} disabled={disabled}>Witness Me!</Button> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const Verify = ({ disabled, onChange, onClick, requestId, verified }) => { | ||
| if (! requestId || verified) { | ||
| return null; | ||
| } | ||
| return ( | ||
| <div className="body-content"> | ||
| <p className="convo">Almost there! What's the super secret code?</p> | ||
| <FormGroup> | ||
| <Label for="code">SMS Code</Label> | ||
| <Input type="nummber" name="code" id="code" placeholder="e.g.: 4477" onChange={(event) => onChange(event)} /> | ||
| </FormGroup> | ||
| <Button color="info" onClick={() => onClick()} disabled={disabled}>Verify Me!</Button> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const Rick = ({ verified }) => { | ||
| if (! verified) { | ||
| return null; | ||
| } | ||
| return ( | ||
| <div className="body-content"> | ||
| <p className="convo">OMG! You did it!</p> | ||
| <Button color="info" onClick={() => {window.location = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';}}>Show me my quest!</Button> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| class App extends Component { | ||
| state = { | ||
| phone: null, | ||
| code: null, | ||
| verified: null, | ||
| request_id: null, | ||
| loading: false | ||
| } | ||
| handleRequest() { | ||
| this.setState({ loading: true }); | ||
| axios.post('/request', { | ||
| number: this.state.phone | ||
| }) | ||
| .then((response) => { | ||
| this.setState({ request_id: response.data.request_id }); | ||
| this.setState({ loading: false }); | ||
| }) | ||
| .catch((error) => { | ||
| console.log(error); | ||
| this.setState({ loading: false }); | ||
| }); | ||
| } | ||
| handleVerify() { | ||
| this.setState({ loading: true }); | ||
| axios.post('/verify', { | ||
| request_id: this.state.request_id, | ||
| code: this.state.code | ||
| }) | ||
| .then((response) => { | ||
| this.setState({ verified: response.data.verified }); | ||
| this.setState({ loading: false }); | ||
| }) | ||
| .catch((error) => { | ||
| console.log(error); | ||
| this.setState({ loading: false }); | ||
| }); | ||
| } | ||
| render() { | ||
| return ( | ||
| <div className="App"> | ||
| <header className="App-header"> | ||
| <Guinea | ||
| requestId={this.state.request_id} | ||
| verified={this.state.verified} /> | ||
| <h1 className="App-title">Hello!</h1> | ||
| <p>I'm so excited you made it! I have a quest for you!</p> | ||
| </header> | ||
|
|
||
| <Request | ||
| requestId={ this.state.request_id } | ||
| disabled={ this.state.loading } | ||
| onClick={() => this.handleRequest()} | ||
| onChange={(event) => this.setState({ phone: event.target.value })} | ||
| /> | ||
|
|
||
| <Verify | ||
| requestId={ this.state.request_id } | ||
| verified={ this.state.verified } | ||
| disabled={ this.state.loading } | ||
| onClick={() => this.handleVerify()} | ||
| onChange={(event) => this.setState({ code: event.target.value })} /> | ||
|
|
||
| <Rick verified={ this.state.verified } /> | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export default App; |
| @@ -0,0 +1,9 @@ | ||
| import React from 'react'; | ||
| import ReactDOM from 'react-dom'; | ||
| import App from './App'; | ||
|
|
||
| it('renders without crashing', () => { | ||
| const div = document.createElement('div'); | ||
| ReactDOM.render(<App />, div); | ||
| ReactDOM.unmountComponentAtNode(div); | ||
| }); |
| @@ -0,0 +1,5 @@ | ||
| body { | ||
| margin: 0; | ||
| padding: 0; | ||
| font-family: sans-serif; | ||
| } |
| @@ -0,0 +1,9 @@ | ||
| import React from 'react'; | ||
| import ReactDOM from 'react-dom'; | ||
| import './index.css'; | ||
| import App from './App'; | ||
| import registerServiceWorker from './registerServiceWorker'; | ||
| import 'bootstrap/dist/css/bootstrap.min.css'; | ||
|
|
||
| ReactDOM.render(<App />, document.getElementById('root')); | ||
| registerServiceWorker(); |
| @@ -0,0 +1,117 @@ | ||
| // In production, we register a service worker to serve assets from local cache. | ||
|
|
||
| // This lets the app load faster on subsequent visits in production, and gives | ||
| // it offline capabilities. However, it also means that developers (and users) | ||
| // will only see deployed updates on the "N+1" visit to a page, since previously | ||
| // cached resources are updated in the background. | ||
|
|
||
| // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. | ||
| // This link also includes instructions on opting out of this behavior. | ||
|
|
||
| const isLocalhost = Boolean( | ||
| window.location.hostname === 'localhost' || | ||
| // [::1] is the IPv6 localhost address. | ||
| window.location.hostname === '[::1]' || | ||
| // 127.0.0.1/8 is considered localhost for IPv4. | ||
| window.location.hostname.match( | ||
| /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ | ||
| ) | ||
| ); | ||
|
|
||
| export default function register() { | ||
| if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { | ||
| // The URL constructor is available in all browsers that support SW. | ||
| const publicUrl = new URL(process.env.PUBLIC_URL, window.location); | ||
| if (publicUrl.origin !== window.location.origin) { | ||
| // Our service worker won't work if PUBLIC_URL is on a different origin | ||
| // from what our page is served on. This might happen if a CDN is used to | ||
| // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 | ||
| return; | ||
| } | ||
|
|
||
| window.addEventListener('load', () => { | ||
| const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; | ||
|
|
||
| if (isLocalhost) { | ||
| // This is running on localhost. Lets check if a service worker still exists or not. | ||
| checkValidServiceWorker(swUrl); | ||
|
|
||
| // Add some additional logging to localhost, pointing developers to the | ||
| // service worker/PWA documentation. | ||
| navigator.serviceWorker.ready.then(() => { | ||
| console.log( | ||
| 'This web app is being served cache-first by a service ' + | ||
| 'worker. To learn more, visit https://goo.gl/SC7cgQ' | ||
| ); | ||
| }); | ||
| } else { | ||
| // Is not local host. Just register service worker | ||
| registerValidSW(swUrl); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| function registerValidSW(swUrl) { | ||
| navigator.serviceWorker | ||
| .register(swUrl) | ||
| .then(registration => { | ||
| registration.onupdatefound = () => { | ||
| const installingWorker = registration.installing; | ||
| installingWorker.onstatechange = () => { | ||
| if (installingWorker.state === 'installed') { | ||
| if (navigator.serviceWorker.controller) { | ||
| // At this point, the old content will have been purged and | ||
| // the fresh content will have been added to the cache. | ||
| // It's the perfect time to display a "New content is | ||
| // available; please refresh." message in your web app. | ||
| console.log('New content is available; please refresh.'); | ||
| } else { | ||
| // At this point, everything has been precached. | ||
| // It's the perfect time to display a | ||
| // "Content is cached for offline use." message. | ||
| console.log('Content is cached for offline use.'); | ||
| } | ||
| } | ||
| }; | ||
| }; | ||
| }) | ||
| .catch(error => { | ||
| console.error('Error during service worker registration:', error); | ||
| }); | ||
| } | ||
|
|
||
| function checkValidServiceWorker(swUrl) { | ||
| // Check if the service worker can be found. If it can't reload the page. | ||
| fetch(swUrl) | ||
| .then(response => { | ||
| // Ensure service worker exists, and that we really are getting a JS file. | ||
| if ( | ||
| response.status === 404 || | ||
| response.headers.get('content-type').indexOf('javascript') === -1 | ||
| ) { | ||
| // No service worker found. Probably a different app. Reload the page. | ||
| navigator.serviceWorker.ready.then(registration => { | ||
| registration.unregister().then(() => { | ||
| window.location.reload(); | ||
| }); | ||
| }); | ||
| } else { | ||
| // Service worker found. Proceed as normal. | ||
| registerValidSW(swUrl); | ||
| } | ||
| }) | ||
| .catch(() => { | ||
| console.log( | ||
| 'No internet connection found. App is running in offline mode.' | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| export function unregister() { | ||
| if ('serviceWorker' in navigator) { | ||
| navigator.serviceWorker.ready.then(registration => { | ||
| registration.unregister(); | ||
| }); | ||
| } | ||
| } |
| @@ -0,0 +1,41 @@ | ||
| const express = require('express'); | ||
| const bodyParser = require('body-parser'); | ||
| const Nexmo = require('nexmo'); | ||
| const cors = require('cors') | ||
|
|
||
| var nexmo = new Nexmo({ | ||
| apiKey: '98d3cc35', | ||
| apiSecret: 'PZwk8InxF72JsYk4' | ||
| }); | ||
|
|
||
| const app = express(); | ||
| app.use(bodyParser.json()); | ||
| app.use(bodyParser.urlencoded({ extended: true })); | ||
| app.use(cors()) | ||
|
|
||
| app.get('/', (req, res) => { | ||
| res.send('nexmo verify'); | ||
| }); | ||
|
|
||
| app.post('/request', (req, res) => { | ||
| let toNumber = req.body.number; | ||
| nexmo.verify.request({ | ||
| number: toNumber, | ||
| brand:'SMS Guinea' | ||
| },(err, responseData) => { | ||
| res.send({request_id: responseData.request_id}); | ||
| }); | ||
| }); | ||
|
|
||
| app.post('/verify', (req, res) => { | ||
| let request_id = req.body.request_id; | ||
| let code = req.body.code; | ||
| nexmo.verify.check({ | ||
| request_id, | ||
| code | ||
| }, (err, responseData) => { | ||
| res.send({ verified: responseData.status === '0' }); | ||
| }); | ||
| }); | ||
|
|
||
| const server = app.listen(4000, () => console.log('listening on 4000')); |