Making a Chrome extension with Vue.js

Making a Chrome extension with Vue.js

In this article, we will make a Chrome extension that displays the weather from the OpenWeatherMap API. We will add a search to let users look up the current weather and forecast from the API and display it in the extension’s popup box.

The most popular web browsers, Chrome and Firefox support extensions. Extensions are small apps that you can add to your browser to get the functionality that is not included in your browser. This makes extending browser functionality very easy. All a user has to do is to add browser add-ons from the online stores like the Chrome Web Store or the Firefox Store to add browser add-ons.

Browser extensions are just normal HTML apps packages in a specific way. This means that we can use HTML, CSS, and JavaScript to build our own extensions.

Chrome and Firefox extensions follow the Web Extension API standard.

In this article, we will make a Chrome extension that displays the weather from the OpenWeatherMap API. We will add a search to let users look up the current weather and forecast from the API and display it in the extension’s popup box.

We will use Vue.js to build the browser extension. To begin building it, we start with creating the project with Vue CLI. Run npx @vue/cli create weather-app to create the project. In the wizard, select Babel and Vuex.

You can register for an API key here. Once you got an API key, create an .env file in the root folder and add VUE_APP_APIKEY as the key and the API key as the value.

Next, we use the vue-cli-plugin-browser-extension to add the files for writing and compiling the Chrome extension. The package settings and details are located at https://www.npmjs.com/package/vue-cli-plugin-browser-extension.

To add it to our project, we run vue add browser-extension to add the files needed to build the extension. The command will change the file structure of our project.

After that command is run, we have to remove some redundant files. We should remove App.vue and main.js from the src folder and leave the files with the same name in the popup folder alone. Then we run npm run serve to build the files as we modify the code.

Next, we have to install the Extension Reload to reload the extension as we are changing the files. Install it from https://chrome.google.com/webstore/detail/extensions-reloader/fimgfedafeadlieiabdeeaodndnlbhid to get hot reloading of our extension in Chrome.

Then we go to the chrome://extensions/ URL in Chrome and toggle on Developer Mode. We should see the Load unpacked button on the top left corner. Click that, and then select the dist folder in our project to load our extension into Chrome.

Next, we have to install some libraries that we will use. We need Axios for making HTTP requests, BootstrapVue for styling, and Vee-Validate for form validation. To install them, we run npm i axios bootstrap-vue vee-validate to install them.

With all the packages installed we can start writing our code. Create CurrentWeather.vue in the components folder and add:

<template>
  <div>
    <br />
    <b-list-group v-if="weather.main">
      <b-list-group-item>Current Temparature: {{weather.main.temp - 273.15}} C</b-list-group-item>
      <b-list-group-item>High: {{weather.main.temp_max - 273.15}} C</b-list-group-item>
      <b-list-group-item>Low: {{weather.main.temp_min - 273.15}} C</b-list-group-item>
      <b-list-group-item>Pressure: {{weather.main.pressure }}mb</b-list-group-item>
      <b-list-group-item>Humidity: {{weather.main.humidity }}%</b-list-group-item>
    </b-list-group>
  </div>
</template>

<script>
import { requestsMixin } from "@/mixins/requestsMixin";

export default {
  name: "CurrentWeather",
  mounted() {},
  mixins: [requestsMixin],
  computed: {
    keyword() {
      return this.$store.state.keyword;
    }
  },
  data() {
    return {
      weather: {}
    };
  },
  watch: {
    async keyword(val) {
      const response = await this.searchWeather(val);
      this.weather = response.data;
    }
  }
};
</script>

<style scoped>
p {
  font-size: 20px;
}
</style>

This component displays the current weather from the OpenWeatherMap API as the keyword from the Vuex store is updated. We will create the Vuex store later. The this.searchWeather function is from the requestsMixin , which is a Vue mixin that we will create. The computed block gets the keyword from the store via this.$store.state.keyword and return the latest value.

Next, create Forecast.vue in the same folder and add:

<template>
  <div>
    <br />
    <b-list-group v-for="(l, i) of forecast.list" :key="i">
      <b-list-group-item>
        <b>Date: {{l.dt_txt}}</b>
      </b-list-group-item>
      <b-list-group-item>Temperature: {{l.main.temp - 273.15}} C</b-list-group-item>
      <b-list-group-item>High: {{l.main.temp_max - 273.15}} C</b-list-group-item>
      <b-list-group-item>Low: {{l.main.temp_min }}mb</b-list-group-item>
      <b-list-group-item>Pressure: {{l.main.pressure }}mb</b-list-group-item>
    </b-list-group>
  </div>
</template>

<script>
import { requestsMixin } from "@/mixins/requestsMixin";

export default {
  name: "Forecast",
  mixins: [requestsMixin],
  computed: {
    keyword() {
      return this.$store.state.keyword;
    }
  },
  data() {
    return {
      forecast: []
    };
  },
  watch: {
    async keyword(val) {
      const response = await this.searchForecast(val);
      this.forecast = response.data;
    }
  }
};
</script>

<style scoped>
p {
  font-size: 20px;
}
</style>

It’s very similar to CurrentWeather.vue . The only difference is that we are getting the current weather instead of the weather forecast.

Next, we create a mixins folder in the src folder and add:

const APIURL = "http://api.openweathermap.org";
const axios = require("axios");

export const requestsMixin = {
  methods: {
    searchWeather(loc) {
      return axios.get(
        `${APIURL}/data/2.5/weather?q=${loc}&appid=${process.env.VUE_APP_APIKEY}`
      );
    },
		
    searchForecast(loc) {
      return axios.get(
        `${APIURL}/data/2.5/forecast?q=${loc}&appid=${process.env.VUE_APP_APIKEY}`
      );
    }
  }
};

These functions are for getting the current weather and the forecast respectively from the OpenWeatherMap API. process.env.VUE_APP_APIKEY is obtained from our .env file that we created earlier.

Next in App.vue inside the popup folder, we replace the existing code with:

<template>
  <div>
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand href="#">Weather App</b-navbar-brand>
    </b-navbar>
    <div class="page">
      <ValidationObserver ref="observer" v-slot="{ invalid }">
        <b-form @submit.prevent="onSubmit" novalidate>
          <b-form-group label="Keyword" label-for="keyword">
            <ValidationProvider name="keyword" rules="required" v-slot="{ errors }">
              <b-form-input
                :state="errors.length == 0"
                v-model="form.keyword"
                type="text"
                required
                placeholder="Keyword"
                name="keyword"
              ></b-form-input>
              <b-form-invalid-feedback :state="errors.length == 0">Keyword is required</b-form-invalid-feedback>
            </ValidationProvider>
          </b-form-group>
					
          <b-button type="submit" variant="primary">Search</b-button>
        </b-form>
      </ValidationObserver>
			
<br />

      <b-tabs>
        <b-tab title="Current Weather">
          <CurrentWeather />
        </b-tab>
        <b-tab title="Forecast">
          <Forecast />
        </b-tab>
      </b-tabs>
    </div>
  </div>
</template>

<script>
import CurrentWeather from "@/components/CurrentWeather.vue";
import Forecast from "@/components/Forecast.vue";

export default {
  name: "App",
  components: { CurrentWeather, Forecast },
  data() {
    return {
      form: {}
    };
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }
      localStorage.setItem("keyword", this.form.keyword);
      this.$store.commit("setKeyword", this.form.keyword);
    }
  },
  beforeMount() {
    this.form = { keyword: localStorage.getItem("keyword") || "" };
  },
  mounted(){
    this.$store.commit("setKeyword", this.form.keyword);
  }
};
</script>

<style>
html {
  min-width: 500px;
}

.page {
  padding: 20px;
}
</style>

We add the BootstrapVue b-navbar here to add a top bar to show the extension’s name. Below that, we added the form for searching the weather info. Form validation is done by wrapping the form in the ValidationObserver component and wrapping the input in the ValidationProvider component. We provide the rule for validation in the rules prop of ValidationProvider . The rules will be added in main.js later.

The error messages are displayed in the b-form-invalid-feedback component. We get the errors from the scoped slot in ValidationProvider . It’s where we get the errors object from.

When the user submits the number, the onSubmit function is called. This is where the ValidationObserver becomes useful as it provides us with the this.$refs.observer.validate() function for form validation.

If isValid resolves to true , then we set the keyword in local storage, and also in the Vuex store by running this.$store.commit(“setKeyword”, this.form.keyword); .

In the beforeMount hook, we set the keyword so that it will be populated when the extension first loads if a keyword was set in local storage. In the mounted hook, we set the keyword in the Vuex store so that the tabs will get the keyword to trigger the search for the weather data.

Then in store.js , we replace the existing code with:

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    keyword: ""
  },
  mutations: {
    setKeyword(state, payload) {
      state.keyword = payload;
    }
  },
  actions: {}
});

to add the Vuex store that we referenced in the components. We have the keyword state for storing the search keyword in the store, and the setKeyword mutation function so that we can set the keyword in our components.

Next in popup/main.js , we replace the existing code with:

import Vue from 'vue'
import App from './App.vue'
import store from "../store";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import BootstrapVue from "bootstrap-vue";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required } from "vee-validate/dist/rules";

/* eslint-disable no-new */

extend("required", required);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
Vue.use(BootstrapVue);

Vue.config.productionTip = false;

new Vue({

store,
  render: h => h(App)
}).$mount("#app");

We added the validation rules that we used in the previous files here, as well as include all the libraries we use in the app. We registered ValidationProvider and ValidationObserver by calling Vue.component so that we can use them in our components. The validation rules provided by Vee-Validate are included in the app so that they can be used by the templates by calling extend from Vee-Validate. We called Vue.use(BootstrapVue) to use BootstrapVue in our app.

Finally in index.html , we replace the existing code with:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>Weather App</title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but vue-chrome-extension-tutorial-app doesn't work properly
        without JavaScript enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

to replace the title.

After all the hard work, we get:

Thank you for reading !

Making a Game in VueJS using Vuex | VueJS Tutorial

Making a Game in VueJS using Vuex | VueJS Tutorial

Today we create a Game in VueJS using Vuex to manage our state. This is an open source community driven game and I would love for you to get involved. We explore the game loop, upgrades, levelling and much more

Today we create a Game in VueJS using Vuex to manage our state. This is an open source community driven game and I would love for you to get involved. We explore the game loop, upgrades, levelling and much more. We looking into VueJS lifecycle methods as well as Vuex mutations, getters and state.

A Javascript Project

Day 15 #31Days31Videos

JWT authentication with VueJS and Vuex

JWT authentication with VueJS and Vuex

In this article, we will build a simple login and login application using Vue.js and state management with Vuex. .Register, login with VueJS and Vuex

Project structure with Vue.js and Vuex

All source code will be placed in / src folder. The src directory will include assets, components for functions and screens, and shared folders for helpers, services, and stores.

Directory helpers

The helpers folder will contain all files that are shared by components with support functions or files that are not located in any directory.

Vue Auth Header

/src/helpers/auth-header.js

Auth header is the return of an HTTP authentication header containing the JSON Web Token (JWT) string of the current logged-in user, retrieved from local storage or cookie. If the user is not logged in, it will return an empty object.

Auth header will be used to make authenticated HTTP requests to the api server using JWT authentication.

export function authHeader() {
    let user = JSON.parse(localStorage.getItem('user'));

    if (user && user.token) {
        return { 'Authorization': 'Bearer ' + user.token };
    } else {
        return {};
    }
}

Vue Router

Path: /src/helpers/router.js

The Vue router defines all routes for the application, containing a function that will always run before the route changes to prevent unauthenticated users from restricted access.

import Vue from 'vue';
import Router from 'vue-router';

import HomePage from '../components/HomePage'
import LoginPage from '../components/LoginPage'
import RegisterPage from '../components/RegisterPage'

Vue.use(Router);

export const router = new Router({
  mode: 'history',
  routes: [
    { path: '/', component: HomePage },
    { path: '/login', component: LoginPage },
    { path: '/register', component: RegisterPage },

     // otherwise redirect to home
    { path: '*', redirect: '/' }
  ]
});

router.beforeEach((to, from, next) => {
  // redirect to login page if not logged in and trying to access a restricted page
  const publicPages = ['/login', '/register'];
  const authRequired = !publicPages.includes(to.path);
  const loggedIn = localStorage.getItem('user');

  if (authRequired && !loggedIn) {
    return next('/login');
  }

  next();
})

Vue Helpers Index

Path: /src/helpers/index.js

The index helpers file groups all export helper together so it can be imported for different files in the app.

When importing we can call import { helper1, helper2, ... } from '../helpers'

export * from './router';
export * from './auth-header';
Vue Services folder

Path: / src / services

The service will contain all the http connections to the backend api for the application, each service will be packaged with api calls for each content type (eg users) and return functions that perform CRUD functions, etc. There are also functions that do not need to call http, eg userService.logout () to only delete items from local storage.

I find encapsulating http calls in the service section to make the code clear, simple, and easier to understand.

Vue User Service

Path: /src/services/user.service.js

The user service encapsulates all api calls to handle CRUD functions for the user, including login, logout, and signup functions. These service functions will be exported through the userService object.

The handleResponse function in the service will check if the http response from the api is 401 _ unauthenticated, it will automatically logout the user. It also includes handling when the JWT token expires or is not validated.

import config from 'config';
import { authHeader } from '../_helpers';

export const userService = {
    login,
    logout,
    register,
    getAll,
    getById,
    update,
    delete: _delete
};

function login(username, password) {
    const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password })
    };

    return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
        .then(handleResponse)
        .then(user => {
            // login successful if there's a jwt token in the response
            if (user.token) {
                // store user details and jwt token in local storage to keep user logged in between page refreshes
                localStorage.setItem('user', JSON.stringify(user));
            }

            return user;
        });
}

function logout() {
    // remove user from local storage to log user out
    localStorage.removeItem('user');
}

function register(user) {
    const requestOptions = {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(user)
    };

    return fetch(`${config.apiUrl}/users/register`, requestOptions).then(handleResponse);
}

function getAll() {
    const requestOptions = {
        method: 'GET',
        headers: authHeader()
    };

    return fetch(`${config.apiUrl}/users`, requestOptions).then(handleResponse);
}


function getById(id) {
    const requestOptions = {
        method: 'GET',
        headers: authHeader()
    };

    return fetch(`${config.apiUrl}/users/${id}`, requestOptions).then(handleResponse);
}

function update(user) {
    const requestOptions = {
        method: 'PUT',
        headers: { ...authHeader(), 'Content-Type': 'application/json' },
        body: JSON.stringify(user)
    };

    return fetch(`${config.apiUrl}/users/${user.id}`, requestOptions).then(handleResponse);
}

// prefixed function name with underscore because delete is a reserved word in javascript
function _delete(id) {
    const requestOptions = {
        method: 'DELETE',
        headers: authHeader()
    };

    return fetch(`${config.apiUrl}/users/${id}`, requestOptions).then(handleResponse);
}

function handleResponse(response) {
    return response.text().then(text => {
        const data = text && JSON.parse(text);
        if (!response.ok) {
            if (response.status === 401) {
                // auto logout if 401 response returned from api
                logout();
                location.reload(true);
            }

            const error = (data && data.message) || response.statusText;
            return Promise.reject(error);
        }

        return data;
    });
}

Vue Services Index

Path: /src/services/index.js

The service index file groups all exported services together so it can easily be imported into other files. import { service1, service2, ... } from '../services'

export * from './user.service';
Store directory

Path: / src / store

Vuex store contains all vuex modules and everything related to vuex store, if you do not know much about vuex, you can refer here.

Vuex manages centralized state in the store, it can be accessed from any component, mutations are committed to update state parts, and actions are dispatched to perform complex functions, which may include Asynchronous calls and multiple mutation

Vuex Account Module

Path: /src/store/modules/account.js

Vuex account module is used to manipulate accounts for state in the store. It contains actions for registering new users, login in and logout, and contains mutations for each state change for each action account.

The state is initialized to the user assigned by the value of the user saved in local storage, which identifies the user who is logged in when the browser reloads or between different browser sessions.

import { userService } from '../_services';
import { router } from '../_helpers';

const user = JSON.parse(localStorage.getItem('user'));
const state = user
    ? { status: { loggedIn: true }, user }
    : { status: {}, user: null };

const actions = {
    login({ dispatch, commit }, { username, password }) {
        commit('loginRequest', { username });
    
        userService.login(username, password)
            .then(
                user => {
                    commit('loginSuccess', user);
                    router.push('/');
                },
                error => {
                    commit('loginFailure', error);
                    dispatch('alert/error', error, { root: true });
                }
            );
    },
    logout({ commit }) {
        userService.logout();
        commit('logout');
    },
    register({ dispatch, commit }, user) {
        commit('registerRequest', user);
    
        userService.register(user)
            .then(
                user => {
                    commit('registerSuccess', user);
                    router.push('/login');
                    setTimeout(() => {
                        // display success message after route change completes
                        dispatch('alert/success', 'Registration successful', { root: true });
                    })
                },
                error => {
                    commit('registerFailure', error);
                    dispatch('alert/error', error, { root: true });
                }
            );
    }
};

const mutations = {
    loginRequest(state, user) {
        state.status = { loggingIn: true };
        state.user = user;
    },
    loginSuccess(state, user) {
        state.status = { loggedIn: true };
        state.user = user;
    },
    loginFailure(state) {
        state.status = {};
        state.user = null;
    },
    logout(state) {
        state.status = {};
        state.user = null;
    },
    registerRequest(state, user) {
        state.status = { registering: true };
    },
    registerSuccess(state, user) {
        state.status = {};
    },
    registerFailure(state, error) {
        state.status = {};
    }
};

export const account = {
    namespaced: true,
    state,
    actions,
    mutations
};

Vuex Alert Module

Path: /src/store/modules/alert.js

Vuex alert module is used to handle alert for state in store. It contains actions and mutations for successful or failed message alert settings, and also for clearing alarms.

In this module, each action alert only commits a mutation, so it will be possible to commit the mutation directly from the vue component.

const state = {
    type: null,
    message: null
};

const actions = {
    success({ commit }, message) {
        commit('success', message);
    },
    error({ commit }, message) {
        commit('error', message);
    },
    clear({ commit }, message) {
        commit('success', message);
    }
};

const mutations = {
    success(state, message) {
        state.type = 'alert-success';
        state.message = message;
    },
    error(state, message) {
        state.type = 'alert-danger';
        state.message = message;
    },
    clear(state) {
        state.type = null;
        state.message = null;
    }
};

export const alert = {
    namespaced: true,
    state,
    actions,
    mutations
};

Module index Path: /src/store/modules/index.js

import Vue from 'vue';
import Vuex from 'vuex';

import { alert } from './modules/alert';
import { account } from './modules/account';

Vue.use(Vuex);

export const store = new Vuex.Store({
  modules: {
    alert,
    account
  }
});

Vuex Store

Path: /src/store/index.js

import Vue from 'vue';
import Vuex from 'vuex';

import { account } from './modules/account';
import { alert } from './modules/alert';

Vue.use(Vuex);

export const store = new Vuex.Store({
  modules: {
    account, alert
  }
});

Vue App Component

Path: /src/App.vue

App component is the root component for vue, it contains html code, routes and notification alerts for the app

<template>
    <div class="jumbotron">
        <div class="container">
            <div class="row">
                <div class="col-sm-6 offset-sm-3">
                    <div v-if="alert.message" :class="`alert ${alert.type}`">{{alert.message}}</div>
                    <router-view></router-view>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
    name: 'app',
    computed: {
        ...mapState({
            alert: state => state.alert
        })
    },
    methods: {
        ...mapActions({
            clearAlert: 'alert/clear' 
        })
    },
    watch: {
        $route (to, from){
            this.clearAlert();
        }
    } 
};
</script>
Component directory

Vue Login Page Component

Path: /src/components/account/LoginPage.vue

Login component renders the login form with the user name and password. It also displays validation messages for invalid fields when the user tries to submit the form. If the form is valid, submit will then call the this.login({ username, password })map to the actionaccount/login

In the created () function, the action account/logout dispatched via the logout () function, will log out the user once it is logged in, and it will be redirected to the login page.

Form validation is done via the VeeValidate library

<template>
    <div>
        <h2>Login</h2>
        <form @submit.prevent="handleSubmit">
            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" v-model="username" name="username" class="form-control" :class="{ 'is-invalid': submitted && !username }" />
                <div v-show="submitted && !username" class="invalid-feedback">Username is required</div>
            </div>
            <div class="form-group">
                <label htmlFor="password">Password</label>
                <input type="password" v-model="password" name="password" class="form-control" :class="{ 'is-invalid': submitted && !password }" />
                <div v-show="submitted && !password" class="invalid-feedback">Password is required</div>
            </div>
            <div class="form-group">
                <button class="btn btn-primary" :disabled="status.loggingIn">Login</button>
                <img v-show="status.loggingIn" />
                <router-link to="/register" class="btn btn-link">Register</router-link>
            </div>
        </form>
    </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
    data () {
        return {
            username: '',
            password: '',
            submitted: false
        }
    },
    computed: {
        ...mapState('account', ['status'])
    },
    created () {
        this.logout();
    },
    methods: {
        ...mapActions('account', ['login', 'logout']),
        handleSubmit (e) {
            this.submitted = true;
            const { username, password } = this;
            if (username && password) {
                this.login({ username, password })
            }
        }
    }
};
</script>
Vue Register Component

Path: /src/components/account/SignupPage.vue

The registration component will render a simple registration form with first name, last name, user name and password. It also displays validation messages for invalid fields when the user submits the form. If the form is valid, submit will call the action account/registerto dispatch data from the form.

<template>
    <div>
        <h2>Register</h2>
        <form @submit.prevent="handleSubmit">
            <div class="form-group">
                <label for="firstName">First Name</label>
                <input type="text" v-model="user.firstName" v-validate="'required'" name="firstName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('firstName') }" />
                <div v-if="submitted && errors.has('firstName')" class="invalid-feedback">{{ errors.first('firstName') }}</div>
            </div>
            <div class="form-group">
                <label for="lastName">Last Name</label>
                <input type="text" v-model="user.lastName" v-validate="'required'" name="lastName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('lastName') }" />
                <div v-if="submitted && errors.has('lastName')" class="invalid-feedback">{{ errors.first('lastName') }}</div>
            </div>
            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" v-model="user.username" v-validate="'required'" name="username" class="form-control" :class="{ 'is-invalid': submitted && errors.has('username') }" />
                <div v-if="submitted && errors.has('username')" class="invalid-feedback">{{ errors.first('username') }}</div>
            </div>
            <div class="form-group">
                <label htmlFor="password">Password</label>
                <input type="password" v-model="user.password" v-validate="{ required: true, min: 6 }" name="password" class="form-control" :class="{ 'is-invalid': submitted && errors.has('password') }" />
                <div v-if="submitted && errors.has('password')" class="invalid-feedback">{{ errors.first('password') }}</div>
            </div>
            <div class="form-group">
                <button class="btn btn-primary" :disabled="status.registering">Register</button>
                <img v-show="status.registering" />
                <router-link to="/login" class="btn btn-link">Cancel</router-link>
            </div>
        </form>
    </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
    data () {
        return {
            user: {
                firstName: '',
                lastName: '',
                username: '',
                password: ''
            },
            submitted: false
        }
    },
    computed: {
        ...mapState('account', ['status'])
    },
    methods: {
        ...mapActions('account', ['register']),
        handleSubmit(e) {
            this.submitted = true;
            this.$validator.validate().then(valid => {
                if (valid) {
                    this.register(this.user);
                }
            });
        }
    }
};
</script>
Vue Home Page

Path /src/components/HomePage.vue

The Home Page component is the page that is redirected until the user logs in, logs out or registers.

<template>
  <div>
    <div class="hello">
      Hello
    </div>
    <p>
      <button @click="Logout">Logout</button>
    </p>
  </div>
</template>

<script>
  import { mapActions } from 'vuex'

  export default {
  methods: {
    ...mapActions({
      logout: 'account/logout'
    }),
    Logout () {
      this.logout();
    }
  }
}
</script>

<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

Vue Main Index HTML

Path: /src/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue + Vuex - User Registration and Login Example & Tutorial</title>
    <link href="//netdna.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" />
    <style>
        a { cursor: pointer; }
    </style>
</head>
<body>
    <div id="app"></div>
</body>
</html>

Vue App Index

Path: /src/index.js

import Vue from 'vue';
import VeeValidate from 'vee-validate';

import { store } from './store';
import { router } from './helpers';
import App from './App.vue';

Vue.use(VeeValidate);

new Vue({
    el: '#app',
    router,
    store,
    render: h => h(App)
});

Programming a Javascript Simon Game Tutorial

Programming a Javascript Simon Game Tutorial

In this javascript tutorial, I recorded myself live programming an html5 javascript simon game.

In this javascript tutorial, I recorded myself live programming an html5 javascript simon game.

For those who don't know, I'm a full stack web developer who has been in the industry for over 5 years now. There is a lot of things I have learned along the way and I'd like to share that knowledge with anyone wanting to learn!

like this video if you found it useful and would like to see more videos of the same content.

subscribe to my channel if you are trying to improve your abilities as a web developer, software engineer, or even if you are just learning to code.

Don't forget to turn on those bell notifications!