QR忘れ物アプリ (2) ログインページ [Vue,Vuetify,Firebase]

# はじめに
前回：環境構築
> https://qiita.com/rayan/items/5d04ee2ca7860c220dec

GitHubリポジトリ
> 準備中

# 用いる省略名称
RTDB: Realtime Database

# 操作するファイル
- public
    - __index.js__
- src
    - mixins
        - __databaseOps.js__
    - router
        - __index.js__
    - views
        - __Authentication.vue （新規ファイル）__
    - __App.vue__


## public/index.js
Vuetify用のfont, material-design-iconを追加。

```HTML
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <!-- update the version number as needed -->
    <script src="/__/firebase/8.3.1/firebase-app.js"></script>
    <!-- include only the Firebase features as you need -->
    <script src="/__/firebase/8.3.1/firebase-auth.js"></script>
    <script src="/__/firebase/8.3.1/firebase-database.js"></script>
    <script src="/__/firebase/8.3.1/firebase-functions.js"></script>
    <!-- 
      initialize the SDK after all desired features are loaded, set useEmulator to false
      to avoid connecting the SDK to running emulators.
    -->
    <script src="/__/firebase/init.js?useEmulator=true"></script>
    <script src="https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.js"></script>
    <link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.css" />
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

```

## src/mixins/databaseOps.js
- Firebase RTDB関連のfunctionを格納します
- mixinは複数のcomponentで共通するメソッドの格納する目的で用います
- `Vue.prototype.$userId`にユーザーIDを格納して、どのcomponentからも参照できるようにします。

```javascript
import Vue from 'vue'

export default {
    methods: {
        // User Credentials
        addUserCredentials(data) {
            firebase.database().ref('userCredentials/'+this.$userId).set(data);
        },

        // User Preferences
        initUserPreferences() {    
            var data = {
                'notifyAtLinkOpen': false,
                'notifyNotLostItem': true,
                'whereToNotify': 'email'
            };
            firebase.database().ref('userPreferences/'+this.$userId).set(data);
        },
        setUserPreferences(data) {
            firebase.database().ref('userPreferences/'+this.$userId).set(data);
        },
    }
}
```

## src/views/Authentication.vue
`src/views`内に新しく`Authentication.vue`を作ります。ログイン・新規登録画面のHTMLです。

- ログイン・新規登録画面画面
    - `loader`はログイン・新規登録画面のdiv要素がロード中に"Loading..."と表示するdiv要素です。
    - `firebaseui-auth-container`はログイン・新規登録画面のdiv要素です。
- `loginScreen`
    - mounted
        - `mounted`は要素がDOMに追加されたときに呼ばれます。
    - uiconfig
        - `EmailAuthProvider`: メールアドレスとパスワードによるログインを行います。
    - callbacks
        - `signInSuccessWithAuthResult`
            - サインインに成功したときに呼ばれます。
                - サインイン結果から`uid`、`displayName`、`email`を得ることができます。
            - 新規登録の時には`addUser`を呼び、RTDBにユーザーを登録します。
        - `uiShown`
            - UIがロードされたときに呼ばれます。今回はロードが終了したため、`loader`の表示を消します。

- `addUser`
    - ユーザーを追加し、`userPreferences`を初期設定します。
    - Firebase AuthのユーザーID（userId） を`Vue.prototype.$userId`に格納し、他のcomponentから参照できるようにします。

```vuejs
<template>
  <!-- authentication -->
  <div id="userAuthentication" style="display:block">
    <div id="firebaseui-auth-container"></div>
    <div id="loader">Loading...</div>
  </div>
</template>

<script>
import Vue from 'vue'
import DatabaseOps from '@/mixins/DatabaseOps'

export default {
  name: "Authentication",
  mounted () {
    this.loginScreen();
  },
  methods: {
    // I wanted to include this in a javascript file, but vue router cannot be called outside /src
    loginScreen (){
      // FirebaseUI config.
      var uiConfig = {
        signInOptions: [
          // Leave the lines as is for the providers you want to offer your users.
          //firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          firebase.auth.EmailAuthProvider.PROVIDER_ID,
        ],    
        callbacks: {
          signInSuccessWithAuthResult: (authResult, redirectUrl) => {
            Vue.prototype.$userId = authResult.user.uid;
            if (authResult.additionalUserInfo.isNewUser){ // if new user, register to database
              this.addUser(authResult.user.displayName, authResult.user.email)
            }
            document.getElementById('userAuthentication').style.display = 'none';
            this.$router.push("/MainMenu"); // TBD: router does not work
            return false;
          },
          uiShown: () => {
            document.getElementById('loader').style.display = 'none';
          },
        },
      };
      // Initialize the FirebaseUI Widget using Firebase.
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      // The start method will wait until the DOM is loaded.
      ui.start('#firebaseui-auth-container', uiConfig);
    },    
    addUser(userName, email){
        var data = {
            'email': email,
            'name': userName
        };
        this.addUserCredentials(data);
        this.initUserPreferences();
    },
  },
  data: () => ({
    //
  }),
  mixins: [DatabaseOps]
};
</script>
```

## src/router/index.js
新しく追加した`Authentication.vue`のページの情報を追加します。
また、アクセス時には認証画面へのリダイレクトをするように設定します。
> 参考リンク: https://qiita.com/sin_tanaka/items/ea149a33bd9e4b388241

```javascript
import Vue from "vue";
import VueRouter from "vue-router";

Vue.use(VueRouter);

const routes = [
  {
    path: "*",
    redirect: "/"
  },
  {
    path: "/",
    name: "Authentication",
    component: () =>
      import("@/views/Authentication.vue"),
  },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

router.beforeEach((to, from, next) => {
  let requiresAuth = to.matched.some(record => record.meta.requiresAuth)
  let currentUser = firebase.auth().currentUser
  if (requiresAuth) { // if this page requires auth, redirect to auth page
    if (!currentUser) {
      next({
        path: '/',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // this is required
  }
})

export default router;
```


## src/App.vue
`Authentication.vue`の設定を追加します。

```vuejs
<template>
  <v-app>
    <v-main>
      <Authentication />
    </v-main>
  </v-app>
</template>

<script>
//import HelloWorld from "./components/HelloWorld";
import Authentication from "./views/Authentication";

export default {
  name: "App",

  components: {
    Authentication,
  },
  data: () => ({
    //
  }),
};
</script>
```

# Build
- ビルドし、動作をテストします。動作のテストにはFirebase Emulatorが必要であるため、起動します。

```bash
npm run build
firebase emulators:start
```


- `dist/index.html`をブラウザで閲覧し下の画像のような画面が出ることを確認します。
    - ![auth.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1299854/81216516-af69-498e-6803-2043a18c5197.png)

- アカウント作成実行後、AuthとTRDBに正常にアカウント登録の処理がされていることを確認します。
    - Firebase Authentication Emulator
        - http://localhost:4000/auth にアクセス
        - 登録したメールアドレス、名前のアカウントが追加されていることを確認する。
    - Firebase RTDB Emulator
        - http://localhost:4000/database/{データベース名}/data にアクセス
        - 以下のjsonファイルが生成されていることを確認する。

```json
http://127.0.0.1:9000/?ns=[データベース名]: {
  userCredentials: {
    [Firebase AuthのUID]: {
      email: "[Email]"
      name: "[名前]"
    }
  },
  userPreferences: {
    [Firebase AuthのUID]: {
      notifyAtLinkOpen: false,
      notifyNotLostItem: true,
      whereToNotify: "email"
    }
  }
}
```

# 次回
ユーザー設定確認・変更画面