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
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>
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);
},
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();
}
})
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;
}
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);
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>
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
Top comments (0)