To the Stars with Quasar & Firebase - User Profile
Tags: vue, quasar, firestore, storage
Published: 2020-02-06 19:01:45

## Table Of Contents
1. [Introduction](#intro)
 - 1.1 [Assumptions](#assumptions)
2. [Update User Creation Flow](#userCreation)
 - 2.1 [Setup Cloud Firestore](#firestore)
 - 2.2 [Create the DB Service](#dbService)
 - 2.3 [User Model](#userModel)
 - 2.4 [Update Auth Actions](#authActions) 
3. [User Profile with Vuexfire](#userProfileVuexfire)
 - 3.1 [Why Vuexfire](#whyVuexFire)
 - 3.2 [Vuexfire Installation](#vuexfireInstallation)
 - 3.3 [Vuexfire Integration](#vuexfireIntegration)
4. [Update User Info](#updateUserInfo)
 - 4.1 [Google Cloud Storage Setup](#cloudStorage)
5. [Unbinding Vuexfire Bindings](#unbinding)
6. [Summary](#summary)
7. [Repository](#repo)

### 1. Introduction <a name="intro"></a>
This article builds atop of the initial article, [Initial Service & Structure](https://dev.to/quasar/to-the-stars-with-quasar-firebase-initial-service-structure-1fcf), and [Email Authentication](https://dev.to/quasar/to-the-stars-with-quasar-firebase-email-authentication-37dh) published in the Quasar-Firebase series. In this post, we're going to build something that most applications that handle users have: a user profile. We'll be using Firebase, Firestore,  Vuexfire, and Google's Cloud Storage.

 - 1.1 Assumptions <a name="assumptions"></a>

Before we get started, a few assumptions are in order. In this post, we're going to be building atop of the email authentication article, specifically, state management. Be sure to go over the insights in that post first, or review if needed. Also, one minor addition is we'll be adding Vuexfire bindings in our actions files. Let's update our illustration for consistency.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bph5wj2wuql4jh1i2ldm.png)

*Note: This repo already contains a working Firebase API key. In order to set up your own project, you need to delete the "FIREBASE_CONFIG" attributes within the `.quasar.env.json` file and replace it with your own key from the [first article](https://dev.to/quasar/to-the-stars-with-quasar-firebase-initial-service-structure-1fcf). 

If you already created an account on this Firebase API from the [email authentication article](https://dev.to/quasar/to-the-stars-with-quasar-firebase-email-authentication-37dh) you'll need to use a different email to set up a new account, as that account doesn't have a user in the users collection as you'll learn in this article.*

Be sure to [clone the repo](https://github.com/quasarframework/firebase-sample-apps/) and have the app to follow along with. Navigate to the respective app and run:

```bash
$ npm run dev
```
*A final note, this code is for Vue v2 and Quasar v1.*
### 2. Update User Creation Flow <a name="userCreation"></a>

In our email authentication post, we built a form to allow users to register a new account and also log into the application via their credentials that were supplied during registration. That was a good first step, but now we need to expand the experience so we can build our profile off of additional user information. 

Also, now that we're going to be working more in-depth with our user, we're going to split up our layouts. One called `Basic.vue`, and one called `User.vue`. The user layout will have the logging out functionality and also controlling the opacity of when a user launches their settings modal.

**[/src/layouts/Basic.vue](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/layouts/Basic.vue)**
**[/src/layouts/User.vue](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/layouts/User.vue)**  

[Managing users](https://firebase.google.com/docs/auth/web/manage-users) are possible to some extent through the Firebase authentication record, but we need more fidelity for our user.

Let's use Cloud Firestore as our database and create a new record in a users' collection.

- 2.1 Setup Cloud Firestore <a name="firestore"></a> 

Back in the Firebase console, click on the *Database* section in the left menu, and then create a Cloud Firestore instance.
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/rwkvma4vihfpxpxag4re.png)

Be sure to keep our Firestore instance in *Production Mode*
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/zt6vr38mg785niz1hqqo.png)

Set the location of your server. You can choose whatever works best for your location.
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/exulxofdaa62oo2quu2s.png)

Once you have Firestore set up, it's essential to set some basic [security rules](https://firebase.google.com/docs/rules/get-started) for your data. In your *Rules* tab, enter the following snippet of code:

```
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid != null;
    }
  }
}
```
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/t01e3en3f5z3pybb2jsq.png)

If you don't do this, your call to Firestore to save a User record will fail, and our application will prevent the user from moving forward after successful user registration.
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/sh2hmc92ew3zthppebeu.png)

- 2.2 Create the DB Service <a name="dbService"></a>

Now, a DB service needs to be created. Take a look at the new service:

**[/src/services/firebase/db.js](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/services/firebase/db.js)**

Following the pattern from the Email post, this service allows the application to have a reference to Cloud Firestore. Now, add the reference of the `db` service into our `firebaseService` object in our `index.js` file to keep the service under one namespace, `firebaseService`.


**/src/services/firebase/index.js**
```js
import * as base from '../services/firebase/base.js'
import * as db from '../services/firebase/db.js'
import * as email from '../services/firebase/email.js'

export default Object.assign({}, base, db, email)
```

 - 2.3 User Model <a name="userModel"></a>

Next, create a `User` model.

**[/src/models/User.js](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/models/User.js)**

```js
/** Class representing a User. */
export default class User {
  /**
   * Create a user.
   * @param {String} id - Place holder for a unique Firebase id.
   * @param {String} backgroundPhoto - A generated URL from Google Storage.
   * @param {String} email - A valid email.
   * @param {String} fullName - The user's full name.
   * @param {String} mobile - the user's mobile number.
   * @param {String} profilePhoto - A generated URL from Google Storage.
  */
  id = ''
  backgroundPhoto = ''
  email = ''
  fullName = ''
  mobile = ''
  profilePhoto = ''

  /**
   * @param  {Object} args - User args
   */
  constructor (args) {
    Object.keys(args).forEach((v, i) => {
      this[v] = args[v]
    })

    return {
      ...this
    }
  }
}

```
Here is a basic user class that consumes the supplied arguments and returns an object to send to Cloud Firestore.

 - 2.4 Update Auth Actions <a name="authActions"></a>

Now that there is a user object, the actions file for creating a user can be updated. Assuming email is the authentication method, let's look at the actions file.

**[/src/store/auth/actions.js](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/store/auth/actions.js)**

Similar to the actions file from the last post for doing basic email authentication, a few changes are needed. Import the new User class, add a new method, `addUserToUsersCollection`, and then update our existing method, `createNewUser`.

```js
import { firestoreAction } from 'vuexfire'
import User from '../../models/User.js'

export const addUserToUsersCollection = async (state, userRef) => {
  const
    { email } = state,
    user = new User({ email })
  return userRef.set(user)
}

export const createNewUser = async function ({ dispatch, commit }, data) {
  const $fb = this.$fb
  const { email, password } = data
  const fbAuthResponse = await $fb.createUserWithEmail(email, password)
  const id = fbAuthResponse.user.uid
  const userRef = $fb.userRef('users', id)
  return addUserToUsersCollection({ email }, userRef)
}
```

*A quick note before we move on.*

- The `import` of `firestoreAction` is seen later in our updated `logoutUser` action.

- Accessing `$fb` from `this` is possible because of the use of the `function` keyword, and because we assigned the service back in our `serverConnection` file when we imported `*` for `base`, `email`, and `db`, respectively.

### 3. User Profile with Vuexfire <a name="userProfileVuexfire"></a>

Now that we have the user flow updated, we move the user over to the user profile screen upon the successful creation of a new user record in Firestore via our route command via our `Auth.vue` file.

**/src/pages/Auth.vue**

```js
onSubmit () {
  const { email, password } = this
  this.$refs.emailAuthenticationForm.validate()
    .then(async success => {
      if (success) {
        this.$q.loading.show({
          message: this.isRegistration
            ? 'Registering your account...'
            : 'Authenticating your account...',
          backgroundColor: 'grey',
          spinner: QSpinnerGears,
          customClass: 'loader'
        })
        try {
          if (this.isRegistration) {
            await this.createNewUser({ email, password })
          } else {
            await this.loginUser({ email, password })
          }
          this.$router.push({ path: '/user/profile' })
        } catch (err) {
          console.error(err)
          this.$q.notify({
            message: `An error as occured: ${err}`,
            color: 'negative'
          })
        } finally {
          this.$q.loading.hide()
        }
      }
    })
}

``` 


Here is our profile page.

**[/src/pages/user/Profile.vue](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/pages/user/Profile.vue)**

Before we render the user profile, we want to get the user's data and sync it to our application's store via Vuexfire.

 - 3.1 Why Vuexfire <a name="whyVuexFire"></a>

The Vue core team maintains Vuexfire, so a reasonable assumption here is that their approach to syncing your data against Firestore is well-designed. Vuefire, another similar binding available, is another option. However, over time, as your application grows, and the need for its data to be in your application's store for multiple aspects of the app, it's just easier to keep it in Vuex. 

Ideally, we want to go from this:
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/oxqblbz216x2dngyi65j.png)

to this, with as little as code possible.
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/7gjhg19u43m4vo65b89m.png)


The Firebase SDK does provide an API to keep your local data in sync with any changes happening in the remote database. However, it is more tedious than you can imagine, and it involves many edge cases. Take a look [here](https://vuefire.vuejs.org/vuexfire/#why) at the code needed to perform this operation.

 - 3.2 Vuexfire Installation <a name="vuexfireInstallation"></a>

Simply install Vuexfire in your app:
```base
yarn add vuexfire
# or
npm install vuexfire
```
 - 3.3 Vuexfire Integration <a name="vuexfireIntegration"></a>

Next, integrate the binding package into our store.

**/src/store/index.js**

```js
import Vue from 'vue'
import Vuex from 'vuex'
import { vuexfireMutations } from 'vuexfire'

import auth from './auth'
import common from './common'
import user from './user'

Vue.use(Vuex)

/*
 * If not building with SSR mode, you can
 * directly export the Store instantiation
 */

export default function (/* { ssrContext } */) {
  const Store = new Vuex.Store({
    modules: {
      auth,
      common,
      user
    },
    mutations: {
      ...vuexfireMutations
    },

    // enable strict mode (adds overhead!)
    // for dev mode only
    strict: process.env.DEV
  })

  return Store
}

```

Now that we have the binding connected to our store, we will create a method in the store's user module actions file.

**[/src/store/user/actions.js](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/store/user/actions.js)**

```js
export const getCurrentUser = firestoreAction(({ bindFirestoreRef }, id) => {
  return bindFirestoreRef('currentUser', userRef('users', id))
})
```

Now that we have an access point to get our current user information from our users' collection in Firestore, we'll need to connect this method when Firebase's `onAuthStateChanged` observer fires when the user becomes authenticated. The key to this is setting our `auth` state with a `uid` from Firebase. Then the key can be used anywhere in the app where specific data regarding the user is needed.

Back in our `base.js` service, look at the `handleOnAuthStateChanged` method:

[**/src/services/firebase/base.js**](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/services/firebase/base.js)

```js
export const handleOnAuthStateChanged = async (store, currentUser) => {
  const initialAuthState = isAuthenticated(store)
  // Save to the store
  store.commit('auth/setAuthState', {
    isAuthenticated: currentUser !== null,
    isReady: true,
    uid: (currentUser ? currentUser.uid : '')
  })
```

Remember this method is connected to our `serverConnection` boot file.
[**/src/boot/serverConnection.js**](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/boot/serverConnection.js)

```js
firebaseService.auth().onAuthStateChanged((currentUser) => {
    firebaseService.handleOnAuthStateChanged(store, currentUser)
  }, (error) => {
    console.error(error)
  })
```
 
Once the `uid` is available via our currentUser from our Firebase auth service, we can attach it to our auth state, and commit the mutation in our 
`handleOnAuthStateChanged` method.

[**/src/store/auth/mutations.js**](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/store/auth/mutations.js)

```js
export function setAuthState (state, data) {
  state.isAuthenticated = data.isAuthenticated
  state.isReady = data.isReady
  state.uid = data.uid
}
```

From here, a decision needs to be made *when* to query Firestore for the user's data. Either here in the `handleOnAuthStateChanged`, or later once the protected route has passed the route guard checks, and then perform the query and notify the user that the app is fetching data. In this instance, we're going to start the query here in the base service for the user's profile data. Because we've added the `uid` to the auth state, we can still rely on the `uid` to be available to any protected route before the page renders. This gives any protected view the key needed to query any data related to the user before rending the view, and after Firebase has supplied the `uid` from its Auth service.

```js
export const handleOnAuthStateChanged = async (store, currentUser) => {
// ...

// Get & bind the current user
  if (store.state.auth.isAuthenticated) {
    await store.dispatch('user/getCurrentUser', currentUser.uid)
  }

// ...
}

```

Once the dispatch has completed, the application's `currentUser` is now bound to our Vuex store.

And that's it! Now, all subsequent writes to our user document in Firestore will automatically be kept in sync in our store module with no additional coding.

### 5. Updating User Info <a name="updateUserInfo"></a>

At this point, you should have an app that looks like the image below.
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/x2z89gvdgxpedqmwlf87.png)

Also, if you open up dev tools you will see a console statement outputting the `uid` from our state that is available to the protected page before rendering from our `created` Vue lifecycle method in our `User.vue` file.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fpg77xdknjderotx11si.png)

Now that we have our data from Firestore bound and in-sync, we're ready to move on to the final piece of our user profile feature, uploading files, and updating user fields.

 - 5.1 Google Cloud Storage Setup <a name="cloudStorage"></a>

Head back over to the console and click on the *storage* menu item, and click *Get started*, and follow the rest of the prompts.
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/00wn8eordb5jgvb5e41n.png)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/rov3v603a2nvjd64lfoe.png)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/l8k7g4f4xumyuea6d1i5.png)


 - 5.2 User actions <a name="userActions"></a>

Now that the current user called from Firestore is loaded into our store's user module, it's time to upload a photo to Cloud Storage. First, take a look at the custom component based off of Quasar's `q-uploader`, within the `UserSettings` component.

**[/src/pages/user/profile/UserSettings.vue](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/pages/user/profile/UserSettings.vue)** 


Per the [docs](https://quasar.dev/vue-components/uploader#Supporting-other-services), we can create a custom component to support our Cloud Firestore service modeled after the QUploaderBase mixin. 

Have a look at our custom component **[FBQUploader](https://github.com/quasarframework/firebase-sample-apps/blob/master/data/profile/src/components/FBQUploader.vue)**

Because there are some considerations regarding reusability, multiple file uploads, and other considerations, a separate article highlighting FBQUploader component will be available in the future.

In regards to the user profile content like name, phone number, or anything else for that matter, capture the data and post it to Firestore. Here we can see this in the `UserSettings` component again. First, we capture the data in our `saveUserData` method on the form submission. 

```js
async saveUserData () {
  const { currentUser, email, fullName, mobile } = this

  this.$q.loading.show({
    message: 'Updating your data, please stand by...',
    customClass: 'text-h3, text-bold'
  })

  try {
    await this.updateUserData({
      id: currentUser.id,
      email,
      fullName,
      mobile
    })
  } catch (err) {
    this.$q.notify({
      message: `Looks like a probelm updating your profile: ${err}`,
      color: 'negative'
    })
  } finally {
    this.$q.loading.hide()
    this.setEditUserDialog(false)
  }
}
```

Set up some visual language notifying the user that we're doing an update via Quasar's Loading plugin, massage the data, then pass it over to the user action, `updateUserData`. 

```js
export const updateUserData = async function ({ state }, payload) {
  return userRef('users', payload.id).update(payload)
}
```

Again, once the data is successfully stored in the current user document in the users' collection, your store's user module automatically updates via the Vuexfire binding.

### 6. Unbinding Vuexfire Bindings <a name="unbinding"></a>
Lastly, when the user logs off we need to unbind our Vuexfire bindings.

```js
export const logoutUser = async function ({ commit }, payload) {
  await firestoreAction(({ unbindFirestoreRef }) => { unbindFirestoreRef('users') })
  commit('user/setCurrentUserData', null, { root: true })
  await this.$fb.logoutUser()
}
```

### 7. Summary <a name="summary"></a>
Hopefully, this gave you some insight into the process of creating a user record in Firestore from the Firebase authentication step. Also, setting up a common use case of a user profile while combining the functionality of uploading files to Google's Cloud Storage, as well as harnessing the power of Vuexfire bindings to simplify the syncing process between your Vuex store modules and Cloud Firestore. 


### 8. Repository <a name="repo"></a>
[User Profile Repo](https://github.com/quasarframework/firebase-sample-apps/tree/master/data/profile)