Going Serverless with VueJS Divya Sasidharan
A presentation at GOTO Chicago 2019 in April 2019 in Chicago, IL, USA by Divya
 
                Going Serverless with VueJS Divya Sasidharan
 
                HELLO MY NAME IS Hellot Divya Sasidharan
 
                HELLO MY NAME IS Divya Sasidharan Hellot @shortdiv
 
                 
                 
                 
                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
 
                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
 
                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
 
                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
 
                Act 1 Departure
 
                The Ordinary World DATE SCENE ACT 04/30 1 1
 
                The Ordinary World DATE SCENE ACT 04/30 1 1
 
                The Ordinary World DATE SCENE ACT 04/30 1 1
 
                ✅ Sammy Serverus
 
                 
                 
                 
                Call to Adventure DATE SCENE ACT 04/30 2 1
 
                Call to Adventure DATE SCENE ACT 04/30 2 1
 
                Call to Adventure DATE SCENE ACT 04/30 2 1
 
                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)
 
                 
                chipie.com
 
                 
                Refusal DATE SCENE ACT 04/30 3 1
 
                Refusal DATE SCENE ACT 04/30 3 1
 
                Refusal DATE SCENE ACT 04/30 3 1
 
                We’re gonna need a bigger server rack
 
                 
                 
                 
                 
                😩😩😩😩
 
                 
                 
                 
                Meeting the Mentor DATE SCENE ACT 04/30 4 1
 
                Meeting the Mentor DATE SCENE ACT 04/30 4 1
 
                Meeting the Mentor DATE SCENE ACT 04/30 4 1
 
                Swami Scale-a-lot
 
                Man who run servers without servers, accomplish many things
 
                Crossing the Threshold DATE SCENE ACT 04/30 5 1
 
                Crossing the Threshold DATE SCENE ACT 04/30 5 1
 
                Crossing the Threshold DATE SCENE ACT 04/30 5 1
 
                 
                 
                 
                Act 2 Initiation
 
                Tests, Allies, Enemies DATE SCENE ACT 04/30 1 2
 
                Tests, Allies, Enemies DATE SCENE ACT 04/30 1 2
 
                Tests, Allies, Enemies DATE SCENE ACT 04/30 1 2
 
                |—————-| | THERE | | ARE | | STILL | | SERVERS | | IN | | SERVERLESS| |—————-| (__/) || (•ㅅ•) || / づ
 
                 
                Innermost Cave DATE SCENE ACT 04/30 2 2
 
                Innermost Cave DATE SCENE ACT 04/30 2 2
 
                Innermost Cave DATE SCENE ACT 04/30 2 2
 
                 
                 
                 
                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
 
                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
 
                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}) });
 
                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 } } } }
 
                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| |—————-| (_/) || (•ㅅ•) || / づ
 
                 
                exports.handler = function(event, context, callback) { console.log(event) callback(null, { statusCode: 200, body: JSON.stringify({ msg: “Hello, World!” }) }) } functions/hello.js
 
                yarn add netlify-cli
 
                netlify functions:create
 
                [build] command = “yarn build” functions = “functions” publish = “dist” netlify.toml
 
                 
                /.netlify/functions/{function_name}
 
                /.netlify/functions/hello
 
                /.netlify/functions/yelp-it
 
                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
 
                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
 
                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; }, 
                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; }, 
                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 },
 
                reviews.js export const state = { … } export const mutations = { … } export const actions = { … } export const getters = { … } src/state/reviews.js
 
                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) })
 
                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) })
 
                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) })
 
                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) })
 
                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) })
 
                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>
 
                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>
 
                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>
 
                 
                 
                reviews.js export const state = { … } export const mutations = { … } export const actions = { … } export const getters = { … } src/state/reviews.js
 
                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)
 
                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);
 
                Ordeal DATE SCENE ACT 04/30 3 2
 
                Ordeal DATE SCENE ACT 04/30 3 2
 
                Ordeal DATE SCENE ACT 04/30 3 2
 
                 
                Netlify Identity GoTrue
 
                auth.js export const state = { … } export const mutations = { … } export const actions = { … } export const getters = { … } src/state/auth.js
 
                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 = {
 
                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 = {
 
                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) {
 
                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) {
 
                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) => {
 
                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”;
 
                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”;
 
                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);
 
                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
 
                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
 
                Reward DATE SCENE ACT 04/30 4 2
 
                Reward DATE SCENE ACT 04/30 4 2
 
                Reward DATE SCENE ACT 04/30 4 2
 
                 
                 
                Password????
 
                Act 3 Return
 
                The Road Back DATE SCENE ACT 04/30 1 3
 
                The Road Back DATE SCENE ACT 04/30 1 3
 
                The Road Back DATE SCENE ACT 04/30 1 3
 
                There is no cloud, just someone else’s server
 
                Resurrection/ Return with Elixir DATE SCENE ACT 04/30 2 3
 
                Resurrection/ Return with Elixir DATE SCENE ACT 04/30 2 3
 
                Resurrection/ Return with Elixir DATE SCENE ACT 04/30 2 3
 
                👏 👏 👏 👏 👏 👏 ⚡ ⚡ ⚡ 👏 👏 👏 👏
 
                chipie.netlify.live
 
                % github.com/shortdiv/chipie Divya Sasidharan @shortdiv
 
                