Nuxt.js で作った国別クイズにログイン機能とマイページを追加してみた

## はじめに
この記事は、こちらの記事の続きとなります！

https://qiita.com/Tekere/items/6bc1ff17e2c928a7d117


## 追加した機能
- Firebaseの認証機能を使ってログイン機能を追加
- ログイン済みのユーザーはマイページにアクセスでき、最近の成績、平均点などを見られる

## 実装したソースコード

`store/index.js`
ここでは、ユーザーの情報と、ローディング中の状態管理をしています。

```js
import firebase from '~/plugins/firebase'

export const state = () => ({
  loginUser: null,
  isLoading: false,
})

export const getters = {
  loginUser: (state) => state.loginUser,
  isLoading: (state) => state.isLoading,
}

export const mutations = {
  setLoginUser(state, user) {
    // userから必要なものだけを抜き出します
    const { uid, email, displayName, photoURL } = user
    state.loginUser = { uid, email, displayName, photoURL }
  },
  deleteLoginUser(state) {
    // ログアウト後はstateからloginUserを削除します
    state.loginUser = null
  },
  stopLoading(state) {
    state.isLoading = true
  },
}

export const actions = {
  setLoginUser({ commit }, user) {
    commit('setLoginUser', user)
  },
  deleteLoginUser({ commit }) {
    commit('deleteLoginUser')
  },
  login() {
    // google認証
    const googleAuthProvider = new firebase.auth.GoogleAuthProvider()
    firebase.auth().signInWithRedirect(googleAuthProvider)
  },
  logout({ commit }) {
    firebase
      .auth()
      .signOut()
      .then(() => {
        commit('deleteLoginUser')
        this.$router.push('/')
      })
  },
  stopLoading({ commit }) {
    commit('stopLoading')
  },
}

```


`store/result.js`
ここでは、クイズの成績を管理します


```js
import firebase from '~/plugins/firebase'

export const state = () => ({
  results: [],
})

export const getters = {
  results: (state) => state.results,
  countTypeOfQuiz: (state) => {
    const res = state.results.reduce(
      (acc, el) => {
        if (el.typeOfQuiz === 'flag') acc.flagCount++
        if (el.typeOfQuiz === 'region') acc.regionCount++
        return acc
      },
      { regionCount: 0, flagCount: 0 }
    )
    return res
  },
  averageOfCorrectAnswers: (state) => {
    const res = state.results.reduce((acc, el) => {
      acc += el.correctAnswerCount
      return acc
    }, 0)
    if (res) return (res / state.results.length).toFixed(2)
    else return 0
  },
}

export const mutations = {
  addResult(state, result) {
    // ascで古い順にcommitされていく & 一時的にstateに入れるものは最新なのでunshiftを使う
    state.results.unshift(result)
  },
}

export const actions = {
  fetchResult({ commit }, uid) {
    return new Promise(function (resolve) {
      firebase
        .firestore()
        .collection(`users/${uid}/results`)
        .orderBy('createdAt', 'asc')
        .get()
        .then((snapshot) => {
          snapshot.forEach((doc) => {
            commit('addResult', doc.data())
          })
        })
        .then(() => {
          resolve()
        })
    })
  },

  addResult({ commit }, { uid, typeOfQuiz, correctAnswers }) {
    if (uid) {
      const targetDate = new Date()
      const resultObj = {
        typeOfQuiz,
        correctAnswerCount: correctAnswers,
        createdAt: targetDate,
      }
      firebase.firestore().collection(`users/${uid}/results`).add(resultObj)
      commit('addResult', resultObj)
    }
  },
}

```

`default.vue`


```vue
<template>
  <div id="container">
    <Nuxt v-if="isLoading" />
    <loading v-else>loading</loading>
  </div>
</template>
<script>
import firebase from 'firebase'
import { mapGetters, mapActions } from 'vuex'
import Loading from '@/components/Loading'
export default {
  components: {
    Loading,
  },
  computed: {
    ...mapGetters(['loginUser']),
    ...mapGetters(['isLoading']),
  },
  created() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        const that = this
        new Promise(function (resolve) {
          that.setLoginUser(user)
          resolve()
        })
          .then(function () {
            return new Promise(function (resolve) {
              that.fetchResult(that.loginUser.uid).then(() => {
                resolve()
              })
            })
          })
          .then(function () {
            that.stopLoading()
          })
      } else {
        this.deleteLoginUser()
        this.stopLoading()
      }
    })
  },
  methods: {
    ...mapActions(['setLoginUser', 'deleteLoginUser', 'stopLoading']),
    ...mapActions('result', ['fetchResult']),
  },
}
</script>
```


`pages/mypage.vue`

```vue
<template>
  <div id="mypage">
    <div class="nav">
      <router-link to="/">TOP</router-link>
      <a class="logout" @click.prevent="logout">ログアウト</a>
    </div>
    <div class="mypage-inner">
      <h1 class="title is-1">COUNTRY QUIZ</h1>
      <div class="mypage-box">
        <div class="user-info">
          <div v-if="loginUser">
            <img :src="loginUser.photoURL" alt="" />
            <p>{{ loginUser.displayName }}</p>
          </div>
        </div>
        <div class="results">
          <table class="table is-striped is-fullwidth">
            <thead>
              <tr>
                <th>日付</th>
                <th>挑戦したクイズ</th>
                <th>結果</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="result in results" :key="result.nanoseconds">
                <td>{{ result.createdAt | convertCreatedAt }}</td>
                <td>{{ result.typeOfQuiz | convertTypeOfQuiz }}</td>
                <td>{{ result.correctAnswerCount }}/5</td>
              </tr>
            </tbody>
          </table>
        </div>
        <div class="graf-box">
          <div class="pie">
            <pie-chart :count-type-of-quiz="countTypeOfQuiz"></pie-chart>
          </div>
          <div class="average">
            <h4>平均</h4>
            <p>{{ averageOfCorrectAnswers }}</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import moment from 'moment'
import PieChart from '@/components/PieChart'
export default {
  components: {
    PieChart,
  },
  data() {
    return {
      typeOfQuizObj: null,
    }
  },
  computed: {
    ...mapGetters(['loginUser']),
    ...mapGetters('result', [
      'results',
      'countTypeOfQuiz',
      'averageOfCorrectAnswers',
    ]),
  },
  created() {
    if (!this.loginUser) {
      this.$router.push('/')
    }
  },
  methods: {
    ...mapActions(['logout']),
    ...mapActions('result', ['fetchResult', 'addResult']),
  },
  filters: {
    convertCreatedAt(val) {
      if (val.seconds) {
        const dateTime = moment.unix(val.seconds).format('YYYY/M/D')
        return dateTime
      } else {
        const dateTime = moment(val.seconds).format('YYYY/M/D')
        return dateTime
      }
    },
    convertTypeOfQuiz(val) {
      if (val === 'region') return '地域区分クイズ'
      else if (val === 'flag') return '国旗クイズ'
      else return 'error'
    },
  },
}
</script>
```