CurateBot Devlog 5: Adding Firebase Auth Logout and Vuetify Snackbars
Tags: devjournal, twitter, typescript, vue
Published: 2021-01-06 10:12:15

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](https://github.com/meseta/curatebot/tree/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f)

![login](https://dev-to-uploads.s3.amazonaws.com/i/un8ykerpkqepf26cdd8i.gif)

# 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](https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/store/alert/index.ts) 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](https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/components/Alerts.vue) 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

```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>
```

This Vue module uses vuetify's <v-snackbar> 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](https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/store/auth/index.ts
):

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

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](https://router.vuejs.org/guide/advanced/navigation-guards.html) 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](https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/router/index.ts), 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:

```ts
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();
  }
})
```

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](https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/store/auth/types.ts) like this:

```ts
export interface UserData {
  profileImage: string;
  name: string;
  handle: string;
  id: string;
  accessToken: string;
  secret: string;
}
```

And when the user actually does a login, I [capture these](https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/store/auth/index.ts) details like this:
```ts
      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);
```

This allows me to add a little [logged-in user display](https://github.com/meseta/curatebot/blob/be289d9bd3d91ecdea1ab6d75ae0c76982788a1f/web/src/components/AppBar.vue) to show the logged-in user in the top right corner of the app-bar!

```vue
    <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>
```

___

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](https://dev-to-uploads.s3.amazonaws.com/i/log3nnr60omffgfxcqzi.gif)