Going Serverless with VueJS

A presentation at GOTO Chicago 2019 in April 2019 in Chicago, IL, USA by Divya

Slide 1

Slide 1

Going Serverless with VueJS Divya Sasidharan

Slide 2

Slide 2

HELLO MY NAME IS Hellot Divya Sasidharan

Slide 3

Slide 3

HELLO MY NAME IS Divya Sasidharan Hellot @shortdiv

Slide 4

Slide 4

Slide 5

Slide 5

Slide 6

Slide 6

Slide 7

Slide 7

The Ordinary World The call to adventure Refusal Meeting the Mentor Crossing the threshold Tests, Allies, Enemies Innermost Cave Ordeal Reward The road back Resurrection Return with the Elixir

Slide 8

Slide 8

The Ordinary World The call to adventure Refusal Meeting the Mentor Crossing the threshold Tests, Allies, Enemies Innermost Cave Ordeal Reward The road back Resurrection Return with the Elixir DEPARTURE

Slide 9

Slide 9

The Ordinary World The call to adventure Refusal DEPARTURE Meeting the Mentor Crossing the threshold Tests, Allies, Enemies Innermost Cave Ordeal Reward The road back Resurrection Return with the Elixir INITIATION

Slide 10

Slide 10

The Ordinary World The call to adventure Refusal DEPARTURE Meeting the Mentor Crossing the threshold Tests, Allies, Enemies Innermost Cave INITIATION Ordeal Reward The road back Resurrection Return with the Elixir RETURN

Slide 11

Slide 11

Act 1 Departure

Slide 12

Slide 12

The Ordinary World DATE SCENE ACT 04/30 1 1

Slide 13

Slide 13

The Ordinary World DATE SCENE ACT 04/30 1 1

Slide 14

Slide 14

The Ordinary World DATE SCENE ACT 04/30 1 1

Slide 15

Slide 15

✅ Sammy Serverus

Slide 16

Slide 16

Slide 17

Slide 17

Slide 18

Slide 18

Slide 19

Slide 19

Call to Adventure DATE SCENE ACT 04/30 2 1

Slide 20

Slide 20

Call to Adventure DATE SCENE ACT 04/30 2 1

Slide 21

Slide 21

Call to Adventure DATE SCENE ACT 04/30 2 1

Slide 22

Slide 22

Orange News 1. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 2. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 1 comments 3. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 4. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 5. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 6. 7. 8. 9. 10. 11. 12. 13. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 14. Some news (github.com) 100000 points by somedude 3 hours ago | hide | 100000 comments 15. Some news (github.com)

Slide 23

Slide 23

Slide 24

Slide 24

chipie.com

Slide 25

Slide 25

Slide 26

Slide 26

Refusal DATE SCENE ACT 04/30 3 1

Slide 27

Slide 27

Refusal DATE SCENE ACT 04/30 3 1

Slide 28

Slide 28

Refusal DATE SCENE ACT 04/30 3 1

Slide 29

Slide 29

We’re gonna need a bigger server rack

Slide 30

Slide 30

Slide 31

Slide 31

Slide 32

Slide 32

Slide 33

Slide 33

Slide 34

Slide 34

😩😩😩😩

Slide 35

Slide 35

Slide 36

Slide 36

Slide 37

Slide 37

Slide 38

Slide 38

Meeting the Mentor DATE SCENE ACT 04/30 4 1

Slide 39

Slide 39

Meeting the Mentor DATE SCENE ACT 04/30 4 1

Slide 40

Slide 40

Meeting the Mentor DATE SCENE ACT 04/30 4 1

Slide 41

Slide 41

Swami Scale-a-lot

Slide 42

Slide 42

Man who run servers without servers, accomplish many things

Slide 43

Slide 43

Crossing the Threshold DATE SCENE ACT 04/30 5 1

Slide 44

Slide 44

Crossing the Threshold DATE SCENE ACT 04/30 5 1

Slide 45

Slide 45

Crossing the Threshold DATE SCENE ACT 04/30 5 1

Slide 46

Slide 46

Slide 47

Slide 47

Slide 48

Slide 48

Slide 49

Slide 49

Act 2 Initiation

Slide 50

Slide 50

Tests, Allies, Enemies DATE SCENE ACT 04/30 1 2

Slide 51

Slide 51

Tests, Allies, Enemies DATE SCENE ACT 04/30 1 2

Slide 52

Slide 52

Tests, Allies, Enemies DATE SCENE ACT 04/30 1 2

Slide 53

Slide 53

|—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (__/) || (•ㅅ•) || / づ

Slide 54

Slide 54

Slide 55

Slide 55

Innermost Cave DATE SCENE ACT 04/30 2 2

Slide 56

Slide 56

Innermost Cave DATE SCENE ACT 04/30 2 2

Slide 57

Slide 57

Innermost Cave DATE SCENE ACT 04/30 2 2

Slide 58

Slide 58

Slide 59

Slide 59

Slide 60

Slide 60

Slide 61

Slide 61

1 const express = require(‘express’), yelp = require(‘yelp-fusion’), 2 port = 8000, 3 app = express(); 4 5 6 app.use((req, res, next) => { res.header(“Access-Control-Allow-Origin”, “*”); 7 res.header(“Access-Control-Allow-Headers”, “Origin, X-Requested 8 -with, Content-Type, Accept”); 9 next(); 10 }) 11 12 13 app.get(‘/yelpit/:term/:location’, (req, res) => { const clientId = CLIENT_ID; 14 const clientSecret = CLIENT_SECRET; 15 16 yelp.accessToken(clientId, clientSecret) 17 .then((response) => { 18 const client = yelp.client(response.jsonBody.access_token); 19 const searchRequest = { 20 term: req.params.term, 21 limit: 50, 22 location: req.params.location 23 } 24 client.search(searchRequest).then(response => { 25 const firstResult = response.jsonBody.businesses; 26 res.send(firstResult) 27

Slide 62

Slide 62

1 const express = require(‘express’), yelp = require(‘yelp-fusion’), 2 port = 8000, 3 app = express(); 4 5 6 app.use((req, res, next) => { res.header(“Access-Control-Allow-Origin”, “*”); 7 res.header(“Access-Control-Allow-Headers”, “Origin, X-Requested 8 -with, Content-Type, Accept”); 9 next(); 10 }) 11 12 13 app.get(‘/yelpit/:term/:location’, (req, res) => { const clientId = CLIENT_ID; 14 const clientSecret = CLIENT_SECRET; 15 16 yelp.accessToken(clientId, clientSecret) 17 .then((response) => { 18 const client = yelp.client(response.jsonBody.access_token); 19 const searchRequest = { 20 term: req.params.term, 21 limit: 50, 22 location: req.params.location 23 } 24 client.search(searchRequest).then(response => { 25 const firstResult = response.jsonBody.businesses; 26 res.send(firstResult) 27

Slide 63

Slide 63

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 }) app.get(‘/yelpit/:term/:location’, (req, res) => { const clientId = CLIENT_ID; const clientSecret = CLIENT_SECRET; }) yelp.accessToken(clientId, clientSecret) .then((response) => { const client = yelp.client(response.jsonBody.access_token); const searchRequest = { term: req.params.term, limit: 50, location: req.params.location } client.search(searchRequest).then(response => { const firstResult = response.jsonBody.businesses; res.send(firstResult) }) }) app.listen(port, ‘localhost’, function(err) { if(err) {console.log(err)} console.info(Listening on port ${port}) });

Slide 64

Slide 64

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 module.exports = { devServer: { proxy: { ‘^/yelpit/*’: { target: ‘http://localhost:8000/yelpit’, secure: false } } } }

Slide 65

Slide 65

The Chilling Adventures of Functions as a Service |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (_/) || (•ㅅ•) || / づ |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (_/) || (•ㅅ•) || / づ |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (_/) || (•ㅅ•) || / づ |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (_/) || (•ㅅ•) || / づ

Slide 66

Slide 66

Slide 67

Slide 67

exports.handler = function(event, context, callback) { console.log(event) callback(null, { statusCode: 200, body: JSON.stringify({ msg: “Hello, World!” }) }) } functions/hello.js

Slide 68

Slide 68

yarn add netlify-cli

Slide 69

Slide 69

netlify functions:create

Slide 70

Slide 70

[build] command = “yarn build” functions = “functions” publish = “dist” netlify.toml

Slide 71

Slide 71

Slide 72

Slide 72

/.netlify/functions/{function_name}

Slide 73

Slide 73

/.netlify/functions/hello

Slide 74

Slide 74

/.netlify/functions/yelp-it

Slide 75

Slide 75

1 const yelp = require(“yelp-fusion”); 2 3 exports.handler = function(event, context, callback) { const apiKey = “API_KEY”; 4 5 const client = yelp.client(apiKey); 6 7 const { term, location } = event.queryStringParameters; 8 9 const searchRequest = { 10 term: term, 11 location: location 12 }; 13 14 var fetchFromYelp = async function() { 15 try { 16 let res = await client.search(searchRequest); 17 callback(null, { 18 statusCode: 200, 19 body: JSON.stringify({ 20 results: res 21 }) 22 }); 23 } catch (err) { 24 callback(null, { 25 statusCode: 200, 26 body: JSON.stringify({ 27 err: err 28

Slide 76

Slide 76

9 const searchRequest = { 10 term: term, 11 location: location 12 }; 13 14 var fetchFromYelp = async function() { 15 try { 16 let res = await client.search(searchRequest); 17 callback(null, { 18 statusCode: 200, 19 body: JSON.stringify({ 20 results: res 21 }) 22 }); 23 } catch (err) { 24 callback(null, { 25 statusCode: 200, 26 body: JSON.stringify({ 27 err: err 28 }) 29 }); 30 } 31 }; 32 33 fetchFromYelp(); 34 35 }; 36

Slide 77

Slide 77

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <template> <div class=“home”> <div id=”sidebar” v-if=”dataLoaded”> <Slider :yelpData=“yelpData.features” /> </div> </div> </template>

<script> export default { name: “home”, mounted() { axios .get(“/.netlify/functions/yelp-it”, { params: { location: “chicago,il”, term: “pizza” } }) .then(async res => { res = JSON.parse(res.data.results.body); const results = this.geojsonify(res.businesses); this.dataLoaded = true; this.yelpData = results; },

Slide 78

Slide 78

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <template> <div class=“home”> <div id=”sidebar” v-if=”dataLoaded”> <Slider :yelpData=“yelpData.features” /> </div> </div> </template>

<script> export default { name: “home”, mounted() { axios .get(“/.netlify/functions/yelp-it”, { params: { location: “chicago,il”, term: “pizza” } }) .then(async res => { res = JSON.parse(res.data.results.body); const results = this.geojsonify(res.businesses); this.dataLoaded = true; this.yelpData = results; },

Slide 79

Slide 79

11 <script> 12 export default { 13 name: “home”, 14 mounted() { 15 axios 16 .get(“/.netlify/functions/yelp-it”, { 17 params: { 18 location: “chicago,il”, 19 term: “pizza” 20 } 21 22 }) 23 .then(async res => { 24 res = JSON.parse(res.data.results.body); 25 const results = this.geojsonify(res.businesses); 26 this.dataLoaded = true; 27 this.yelpData = results; 28 }, 29 methods: { 30 geojsonify(response) { 31 let features = []; 32 33 response.map(item => { 34 features.push({ 35 type: “Feature”, 36 geometry: { 37 type: “Point”, 38 coordinates: [item.coordinates.longitude, 39 item.coordinates.latitude] 40 },

Slide 80

Slide 80

reviews.js export const state = { … } export const mutations = { … } export const actions = { … } export const getters = { … } src/state/reviews.js

Slide 81

Slide 81

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export const state = { yelpData: null }; export const mutations = { SET_YELP_REVIEWS(state, value) { state.yelpData = value }, } export const actions = { getYelp({ commit }, value) { var geojsonify = function(data) { … } } } axios .get(“/.netlify/functions/yelp-it”, { params: { location: “chicago,il”, term: “pizza” } }) .then(async res => { res = JSON.parse(res.data.results.body); const results = geojsonify(res.businesses); commit(“SET_YELP_REVIEWS”, res.docs) })

Slide 82

Slide 82

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export const state = { yelpData: null }; export const mutations = { SET_YELP_REVIEWS(state, value) { state.yelpData = value } } export const actions = { getYelp({ commit }, value) { var geojsonify = function(data) { … } } } axios .get(“/.netlify/functions/yelp-it”, { params: { location: “chicago,il”, term: “pizza” } }) .then(async res => { res = JSON.parse(res.data.results.body); const results = geojsonify(res.businesses); commit(“SET_YELP_REVIEWS”, res.docs) })

Slide 83

Slide 83

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export const state = { yelpData: null }; export const mutations = { SET_YELP_REVIEWS(state, value) { state.yelpData = value }, } export const actions = { getYelp({ commit }, value) { var geojsonify = function(data) { … } } } axios .get(“/.netlify/functions/yelp-it”, { params: { location: “chicago,il”, term: “pizza” } }) .then(async res => { res = JSON.parse(res.data.results.body); const results = geojsonify(res.businesses); commit(“SET_YELP_REVIEWS”, res.docs) })

Slide 84

Slide 84

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export const state = { yelpData: null }; export const mutations = { SET_YELP_REVIEWS(state, value) { state.yelpData = value }, } export const actions = { getYelp({ commit }, value) { C var geojsonify = function(data) { … } } } axios .get(“/.netlify/functions/yelp-it”, { params: { location: “chicago,il”, term: “pizza” } }) .then(async res => { res = JSON.parse(res.data.results.body); const results = geojsonify(res.businesses); commit(“SET_YELP_REVIEWS”,Cres.docs) })

Slide 85

Slide 85

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export const state = { yelpData: null }; export const mutations = { SET_YELP_REVIEWS(state, value) { state.yelpData = value C }, } export const actions = { getYelp({ commit }, value) { var geojsonify = function(data) { … } } } axios .get(“/.netlify/functions/yelp-it”, { params: { location: “chicago,il”, term: “pizza” } }) .then(async res => { res = JSON.parse(res.data.results.body); const results = geojsonify(res.businesses); commit(“SET_YELP_REVIEWS”, res.docs) })

Slide 86

Slide 86

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template> <div class=“home”> <div id=”sidebar” v-if=”dataLoaded”> <Slider :yelpData=”yelpData.features” /> </div> </div> </template> <script> import { mapState, mapActions } from “vuex”; export default { name: “home”, computed: { …mapState(“yelp”, [“yelpData”]) }, methods: { …mapActions(“reviews”, [“getYelp”]), }, mounted() { this.getYelp() } } </script>

Slide 87

Slide 87

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template> <div class=“home”> <div id=”sidebar” v-if=”dataLoaded”> <Slider :yelpData=”yelpData.features” /> </div> </div> </template> <script> import { mapState, mapActions } from “vuex”; export default { name: “home”, computed: { …mapState(“yelp”, [“yelpData”]) }, methods: { …mapActions(“reviews”, [“getYelp”]), }, mounted() { this.getYelp() } } </script>

Slide 88

Slide 88

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template> <div class=“home”> <div id=”sidebar” v-if=”dataLoaded”> <Slider :yelpData=”yelpData.features” /> </div> </div> </template> <script> import { mapState, mapActions } from “vuex”; export default { name: “home”, computed: { …mapState(“yelp”, [“yelpData”]) }, methods: { …mapActions(“reviews”, [“getYelp”]), }, mounted() { this.getYelp() } } </script>

Slide 89

Slide 89

Slide 90

Slide 90

Slide 91

Slide 91

reviews.js export const state = { … } export const mutations = { … } export const actions = { … } export const getters = { … } src/state/reviews.js

Slide 92

Slide 92

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 firebase.initializeApp(config); const db = firebase.firestore(); export const state = { chiPieReviews: {} }; export const mutations = { SET_CHIPIE_REVIEWS(state, value) { var geojsonify = function() {} var t = {} value.forEach(val => { t[val.id] = val.data() }) debugger state.chiPieReviews = t } }; export const actions = { createRating({ commit }, value) { return new Promise((resolve, reject) => { var user = db .collection(“users”) .doc(value.id); user .collection(“pizza-places”) .doc(value.name)

Slide 93

Slide 93

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 }, setRatings({ dispatch }, value) { return new Promise((resolve, reject) => { var user = db .collection(“users”) .doc(value.id); var docRef = user.collection(“pizza-places”).doc(value.name); docRef .get() .then(doc => { if (doc.exists) { resolve(“oh yeah”); dispatch(“updateRating”, value); } else { dispatch(“createRating”, value); resolve(“empty”); } }) .catch(err => { reject(err); }); }); }, getRatings({ commit }, value) { return new Promise((resolve, reject) => { var user = db .collection(“users”) .doc(value);

Slide 94

Slide 94

Ordeal DATE SCENE ACT 04/30 3 2

Slide 95

Slide 95

Ordeal DATE SCENE ACT 04/30 3 2

Slide 96

Slide 96

Ordeal DATE SCENE ACT 04/30 3 2

Slide 97

Slide 97

Slide 98

Slide 98

Netlify Identity GoTrue

Slide 99

Slide 99

auth.js export const state = { … } export const mutations = { … } export const actions = { … } export const getters = { … } src/state/auth.js

Slide 100

Slide 100

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import GoTrue from “gotrue-js”; import axios from “axios”; const auth = new GoTrue({ APIUrl: “https://chipie.netlify.com/.netlify/identity”, audience: “”, setCookie: false }); export const state = { currentUser: getSavedState(“auth.currentUser”), loading: false, token: null, notifications: [] }; export const mutations = { SET_CURRENT_USER(state, value) { state.currentUser = value; saveState(“auth.currentUser”, value); }, TOGGLE_LOAD(state) { state.loading = !state.loading; } }; export const getters = {

Slide 101

Slide 101

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import GoTrue from “gotrue-js”; C import axios from “axios”; const auth = new GoTrue({ APIUrl: “https://chipie.netlify.com/.netlify/identity”, audience: “”, C setCookie: false }); export const state = { currentUser: getSavedState(“auth.currentUser”), loading: false, token: null, notifications: [] }; export const mutations = { SET_CURRENT_USER(state, value) { state.currentUser = value; saveState(“auth.currentUser”, value); }, TOGGLE_LOAD(state) { state.loading = !state.loading; } }; export const getters = {

Slide 102

Slide 102

31 32 export const actions = { 33 init() { 34 localStorage.removeItem(“auth.currentUser”); 35 }, 36 validate({ commit, state }) { 37 if (!state.currentUser) return Promise.resolve(null); 38 const user = auth.currentUser(); 39 commit(“SET_CURRENT_USER”, user); 40 return user; 41 }, 42 attemptLogin({ commit, dispatch }, credentials) { 43 return new Promise((resolve, reject) => { 44 dispatch(“attemptConfirmation”, credentials).then(() => { 45 auth 46 .login(credentials.email, credentials.password) 47 .then(response => { 48 resolve(response); 49 commit(“SET_CURRENT_USER”, response); 50 }) 51 .catch(error => { 52 reject(error.json); 53 }); 54 }); 55 }); 56 }, 57 58 attemptConfirmation({ commit, dispatch }, credentials) { 59 return new Promise((resolve, reject) => { 60 if (!credentials.token) {

Slide 103

Slide 103

31 32 export const actions = { 33 init() { 34 localStorage.removeItem(“auth.currentUser”); 35 }, 36 validate({ commit, state }) { 37 if (!state.currentUser) return Promise.resolve(null); 38 const user = auth.currentUser(); 39 commit(“SET_CURRENT_USER”, user); 40 return user; 41 }, 42 attemptLogin({ commit, dispatch }, credentials) { 43 return new Promise((resolve, reject) => { 44 dispatch(“attemptConfirmation”, credentials).then(() => { C 45 auth C 46 .login(credentials.email, credentials.password) 47 .then(response => { 48 resolve(response); 49 commit(“SET_CURRENT_USER”, response); C 50 }) 51 .catch(error => { 52 reject(error.json); 53 }); 54 }); 55 }); 56 }, 57 58 attemptConfirmation({ commit, dispatch }, credentials) { 59 return new Promise((resolve, reject) => { 60 if (!credentials.token) {

Slide 104

Slide 104

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 export const mutations = { SET_CURRENT_USER(state, value)C{ state.currentUser = value; C saveState(“auth.currentUser”, value); }, TOGGLE_LOAD(state) { state.loading = !state.loading; } }; export const getters = { isLoggedIn(state) { return !!state.currentUser; } }; export const actions = { init() { localStorage.removeItem(“auth.currentUser”); }, validate({ commit, state }) { if (!state.currentUser) return Promise.resolve(null); const user = auth.currentUser(); commit(“SET_CURRENT_USER”, user); return user; }, attemptLogin({ commit, dispatch }, credentials) { return new Promise((resolve, reject) => {

Slide 105

Slide 105

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <template> <div class=”login-screen”> <div class=”account-login”> <form @submit.prevent=”login()”> <label> <span>Email:</span> <input type=“text” placeholder=“name” v-model=“loginCreds.email” /> </label> <label> <span>Password:</span> <input type=”password” placeholder=”password” v-model=“loginCreds.password” /> </label> <button type=”submit” class=“account-button”>Login</button> </form> </div> </div> </template> <script> import { mapActions } from “vuex”;

Slide 106

Slide 106

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <template> <div class=”login-screen”> <div class=”account-login”> <form @submit.prevent=”login()”> <label> <span>Email:</span> <input type=“text” placeholder=“name” v-model=“loginCreds.email” /> </label> <label> <span>Password:</span> <input type=”password” placeholder=”password” v-model=“loginCreds.password” /> </label> <button type=”submit” class=“account-button”>Login</button> </form> </div> </div> </template> <script> import { mapActions } from “vuex”;

Slide 107

Slide 107

26 27 <script> 28 import { mapActions } from “vuex”; 29 30 export default { name: “LoginAccount”, 31 data() { 32 return { 33 isNewUser: true, 34 loginCreds: { 35 email: null, 36 password: null 37 } 38 } 39 }, 40 methods: { 41 …mapActions(“auth”, [“attemptLogin”]), 42 transferToDashboard() { 43 this.$router.push(this.$route.query.redirect || “/”); 44 }, 45 login() { 46 let token = decodeURIComponent(window.location.search) 47 .substring(1) 48 .split(“confirmation_token=”)[1]; 49 this.attemptLogin({ token, …this.loginCreds }) 50 .then(res => { 51 this.transferToDashboard(); 52 }) 53 .catch(err => { 54 console.log(err);

Slide 108

Slide 108

40 }, 41 methods: { 42 …mapActions(“auth”, [“attemptLogin”]), 43 transferToDashboard() { 44 this.$router.push(this.$route.query.redirect || “/”); 45 }, 46 login() { 47 let token = decodeURIComponent(window.location.search) 48 .substring(1) 49 .split(“confirmation_token=”)[1]; 50 this.attemptLogin({ token, …this.loginCreds }) 51 .then(res => { 52 this.transferToDashboard(); 53 }) 54 .catch(err => { 55 console.log(err); 56 }); 57 }, 58 } 59 } 60 </script> 61 62 63

Slide 109

Slide 109

40 }, 41 methods: { 42 …mapActions(“auth”, [“attemptLogin”]), 43 transferToDashboard() { 44 this.$router.push(this.$route.query.redirect || “/”); 45 }, 46 login() { 47 let token = decodeURIComponent(window.location.search) 48 .substring(1) 49 .split(“confirmation_token=”)[1]; 50 this.attemptLogin({ token, …this.loginCreds }) 51 .then(res => { 52 this.transferToDashboard(); 53 console.log(res); 54 }) 55 .catch(err => { 56 console.log(err); 57 }); 58 }, 59 } 60 } 61 </script> 62 63

Slide 110

Slide 110

Reward DATE SCENE ACT 04/30 4 2

Slide 111

Slide 111

Reward DATE SCENE ACT 04/30 4 2

Slide 112

Slide 112

Reward DATE SCENE ACT 04/30 4 2

Slide 113

Slide 113

Slide 114

Slide 114

Slide 115

Slide 115

Password????

Slide 116

Slide 116

Act 3 Return

Slide 117

Slide 117

The Road Back DATE SCENE ACT 04/30 1 3

Slide 118

Slide 118

The Road Back DATE SCENE ACT 04/30 1 3

Slide 119

Slide 119

The Road Back DATE SCENE ACT 04/30 1 3

Slide 120

Slide 120

There is no cloud, just someone else’s server

Slide 121

Slide 121

Resurrection/ Return with Elixir DATE SCENE ACT 04/30 2 3

Slide 122

Slide 122

Resurrection/ Return with Elixir DATE SCENE ACT 04/30 2 3

Slide 123

Slide 123

Resurrection/ Return with Elixir DATE SCENE ACT 04/30 2 3

Slide 124

Slide 124

👏 👏 👏 👏 👏 👏 ⚡ ⚡ ⚡ 👏 👏 👏 👏

Slide 125

Slide 125

chipie.netlify.live

Slide 126

Slide 126

% github.com/shortdiv/chipie Divya Sasidharan @shortdiv

Slide 127

Slide 127