DEV Community

loading...
Cover image for CurateBot Devlog 5: Adding Firebase Auth Logout and Vuetify Snackbars

CurateBot Devlog 5: Adding Firebase Auth Logout and Vuetify Snackbars

meseta profile image Yuan Gao ・4 min read

To complete the auth system, we have a few loose ends to tie up: Logging out; navigation guards, and alerts. I'll quickly run over the code for these in this post, the corresponding code is here

login

Snackbar alerts

A lot of actions should give users some feedback about successes or failures. I will use snackbars to do it, that provide feedback about success or failure actions. I create a new alerts module in vuex store, because I am considering these alerts to be app-wide states, since they can be generated from anywhere.

The module just contains a series of Mutations to set up the state of the alert, which is defined by:

  • the message, the text to display
  • the color, which switches it between success and error and other colours
  • the isOpen boolean, which indicates whether the alert is displayed
  • the timeout value, which indicates how long this alert should stay onscreen

Then, I create a component that reads this state and renders it.

https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/store/alert/index.ts
https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/components/Alerts.vue

<template>
  <v-snackbar
    v-model="alertState.isOpen"
    :color="alertState.color"
    :timeout="alertState.timeout"
  >
    {{ alertState.message }}
    <template v-slot:action="{ attrs }">
      <v-btn
        text
        v-bind="attrs"
        @click="close()"
      >
        Close
      </v-btn>
    </template>
  </v-snackbar>
</template>

<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import { State, Mutation } from 'vuex-class';
import { AlertState } from '@/store/alert/types';
@Component
export default class SnackbarComponent extends Vue {
  @State(state => state, { namespace: 'alert'}) alertState!: AlertState;
  @Mutation('close', { namespace: 'alert'}) close!: Function;
}
</script>
Enter fullscreen mode Exit fullscreen mode

This Vue module uses vuetify's component, and largely just sticks the various pieces of data from the vuex state into it.

I think perhaps this is one area that could be simplified by using composition API/proxies, or indeed just events; however vuex does fine as well. You could make an argument that splitting the display of alerts into a component, and the state/mutations for the alert in vuex is an unnecessary split. I'll explore better ways of doing this in future.

Logout

Logging out is straightforward, there's a single function to do it: firebase.auth().signout(), but to keep things clean, I also need to clear the user's UID in the state store, as well as pop up an alert to notify of a successful logout, and redirect users to the homepage in case they were on a page that requires login. So a new action is added to the vuex module for auth:

  logout({commit}) {
    firebase.auth().signOut()
    commit('setUid', null);
    commit('alert/showSuccess', "Logged out", {root: true})
    router.push('/').catch(err => err);
  },
Enter fullscreen mode Exit fullscreen mode

The commit('alert/showSuccess') here uses the vuex module we set up earlier for snackbars; and router.push() is part of vue-router, and sends the user to the home page.

Navigation guards

Finally, certain pages require a login, so we need to prevent users from accessing the pages without being logged in (if they're not logged in, certain pages would break because of having no UID). Vue-router has a feature called Navigation Guards which prevent or allow loading of a route depending on certain rules you can set.

I add some new routes, and add an requiresAuth meta property to each route that requires log-in, in my routes file, then I add a navigation guard that checks for this requiresAuth property as well as the vuex auth/isAuthenticated getter which I set up previously:

router.beforeEach((to, from, next) => {
  document.title = to.meta.title

  const requiresAuth = to.matched.some(record => record.meta.requiresAuth)

  if (requiresAuth && !store.getters['auth/isAuthenticated']) {
    store.commit('alert/showError', "Please log in");
    next('/');
  } else {
    next();
  }
})
Enter fullscreen mode Exit fullscreen mode

In plain english: if the page requires authentication, and we're not already authenticated, then show an error "Please log in" and navigate to / (home) rather than the destination we were trying to go.

User profile pic display

If you were following my code closely, you may have noticed that I defined my UserData interface like this:

export interface UserData {
  profileImage: string;
  name: string;
  handle: string;
  id: string;
  accessToken: string;
  secret: string;
}
Enter fullscreen mode Exit fullscreen mode

And when the user actually does a login, I capture these details like this:

      const userData: UserData = {
        profileImage: profile.profile_image_url_https,
        name: profile.name,
        handle: profile.screen_name,
        id: profile.id_str,
        accessToken: credential.accessToken,
        secret: credential.secret
      }
      commit('setUid', uid);
      commit('setUserData', userData);
Enter fullscreen mode Exit fullscreen mode

This allows me to add a little logged-in user display to show the logged-in user in the top right corner of the app-bar!

    <v-sheet v-if="isAuthenticated" color="rgba(0,0,0,0)">
    <v-list-item
      dense 
      two-line
    >
      <v-list-item-content>
        <v-list-item-title class="text-right">{{userData.name}}</v-list-item-title>
        <v-list-item-subtitle class="text-right">@{{userData.handle}}</v-list-item-subtitle>
      </v-list-item-content>
      <v-list-item-avatar>
        <v-avatar>
          <img :src="userData.profileImage" :alt="userData.name">
        </v-avatar>
      </v-list-item-avatar>
    </v-list-item>
    </v-sheet>
Enter fullscreen mode Exit fullscreen mode

Working together, all these little features make logging in/out a more streamlined experience. And especially with the Vue dev-tools extension in the browser, you can scroll through vuex changes and inspect those states and transitions

Login sequence

Discussion (0)

pic
Editor guide