1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

はじめに

この記事は、こちらの記事の続きとなります!

追加した機能

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

実装したソースコード

store/index.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
ここでは、クイズの成績を管理します

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

<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

<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>
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?