jsonzilla / vue2-firebase

Vue book example project

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Vue.js 2 + Firebase

Nova app do livro "Vue.js na prática"

Instalação Inicial

Adicionar o vue-cli

npm i -g vue-cli

Criar o projeto

 vue init webpack blog

cd blog
npm install
npm run dev

Adicionar bibliotecas Vue

npm i -S vuex vue-resource bulma firebase vuefire

Adicionar o arquivo css do Bulma no projeto

// src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

require('../node_modules/bulma/css/bulma.css')

Otimizar o router, para que o import seja carregado sozinho

// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

function load (component) {
  return () => System.import(`../components/${component}.vue`)
}

export default new Router({
  routes: [
    {path: '/', name: 'Hello', component: load('Hello')},
    { path: '*', component: load('Error404') }
  ]
})

Criar o componente Error404

<!-- src/components/Error404.vue -->
<template>
  <div>
    <h1>Not Found</h1>
  </div>
</template>

<script>
export default {

}
</script>

Alterar App.vue para o seguinte desing:

<!-- src/App.vue -->
<template>
  <div id="app">
    <nav class="nav has-shadow" id="top">
      <div class="container">
        <div class="nav-left">
          <a class="nav-item" href="../index.html">
            <h2>Vue.js na prática</h2>
          </a>
        </div>
        <span class="nav-toggle">
        <span></span>
        <span></span>
        <span></span>
        </span>
        <div class="nav-right nav-menu">
          <a class="nav-item is-tab is-active">
          Home
        </a>
          <a class="nav-item is-tab">
          Posts
        </a>
          <a class="nav-item is-tab">
          Comments
        </a>
        </div>
      </div>
    </nav>
    <section class="section">
      <div class="container content">
        <router-view></router-view>
      </div>
    </section>

  </div>
</template>

<script>
  export default {
    name: 'app'
  }

</script>

<style>

</style>

Criar o arquivo src/bradcomp.js

// src/bradcomp.js
// The following code is based off a toggle menu by @Bradcomp
// source: https://gist.github.com/Bradcomp/a9ef2ef322a8e8017443b626208999c1
(function () {
  var burger = document.querySelector('.nav-toggle')
  var menu = document.querySelector('.nav-menu')
  burger.addEventListener('click', function () {
    burger.classList.toggle('is-active')
    menu.classList.toggle('is-active')
  })
})()

Incluir o bradcomp.js no main.js:

// src/main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

require('../node_modules/bulma/css/bulma.css')
require('./bradcomp.js')

Instalando o font-awesome

Pode se pelo npn e na mesma forma que o bulma foi feito. Vamos diferenciar para ver outra forma, através do CDN. No arquivo index.html, adicione o viewport e o cdn do fontawesome:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
    <title>blog</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

Implementando o Vuex

Crie o diretório src/store e os arquivos:

// src/store/actions.js

export const simpleAction = ({ commit }, text) => {
  commit('SimpleMutation', { text })
}
// src/store/getters.js

export const getSimpleText = state => {
  return state.simpleText
}
// src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

import * as getters from './getters'
import * as actions from './actions'
import { state, mutations } from './mutations'

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations
})
// src/store/mutations.js

/* STATE */
export const state = {
  simpleText: 'Hello From Vuex'
}

export const mutations = {

  SimpleMutation (state, { text }) {
    state.simpleText = text
  }

}

Seguindo o fluxo vuex:

Temos:

<template>
  <div>
    {{$store.getters.getSimpleText}}
    <button @click="changeSimpleText">Change</button>
  </div>
</template>

<script>
  export default {
    name: 'hello',
    data () {
      return {
        msg: 'Welcome to Your Vue.js App'
      }
    },
    methods: {
      changeSimpleText: function () {
        this.$store.dispatch('simpleAction', 'Text Changed')
      }
    }
  }

</script>

Vue resource

// src/main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import VueResource from 'vue-resource'

Vue.config.productionTip = false

Vue.use(VueResource)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

require('../node_modules/bulma/css/bulma.css')
require('./bradcomp.js')

Arquivo de configuração

Criar o arquivo src/config.js

// src/config.js
export default {
  url: 'http://localhost:8080'
}

Usar onde quiser:

import config from '../config';

Firebase (Database)

Acessar https://console.firebase.google.com e criar uma conta caso seja necessário.

Crie o projeto

Clique para adicionar o firebase no projeto web

Observe o JSON de configuração. Copie-o!

Permita que qualquer um possa acessar o Database:

{
  "rules": {
    ".read": "auth == null",
    ".write": "auth == null"
  }
}

Também pode ser:

{
  "rules": {
    ".read": "true",
    ".write": "true"
  }
}

E a configuração do firebase+VueFire:

// src/main.js
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import VueResource from 'vue-resource'
import VueFire from 'vuefire'
import firebase from 'firebase'

Vue.config.productionTip = false

Vue.use(VueResource)
Vue.use(VueFire)

firebase.initializeApp({
  apiKey: 'AIzaSyC17mIsDkk38TZGnI9mBjzFIoCu904snn0',
  authDomain: 'vue2napratica.firebaseapp.com',
  databaseURL: 'https://vue2napratica.firebaseio.com',
  projectId: 'vue2napratica',
  storageBucket: 'vue2napratica.appspot.com',
  messagingSenderId: '779719236528'
})

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

require('../node_modules/bulma/css/bulma.css')
require('./bradcomp.js')

Para usar o firebase:

<template>
  <div>
    <input type="text" v-model="post.title"></input>
    <button @click="addPost">Add</button>
    <pre>{{posts}}</pre>
  </div>
</template>

<script>
  import firebase from 'firebase'

  export default {
    name: 'hello',
    data () {
      return {
        post: {
          title: ''
        }
      }
    },
    firebase: {
      posts: firebase.database().ref('posts')
    },
    methods: {
      addPost: function () {
        this.$firebaseRefs.posts.push(this.post).then(
          (r) => {
            this.post = { title: '' }
          }
        )
      }
    }
  }

</script>

Autenticação pelo Github

O Firebase promove vários tipos de autenticação. Vamos usar a do Github. Após obter a autenticação recebemos um token que deve ser guardado no vuex.

Primeiro é preciso ter uma conta no Github para criar um aplicativo GitHub:

Após criar o aplicativo oAuth do Github você tem acesso ao ClientID e Client Secret que deve ser copiado e colado no projeto Firebase. No Firebase, acesse Authentication e Métodos de Login, encontre o item GitHub e clique nele. Forneça os dados de acordo com a imagem a seguir:

Apenas como teste, vamos voltar no componente Hello.vue para testar o login:

<script>
  import firebase from 'firebase'

  export default {
    name: 'hello',
    data () {
      return {
        post: {
          title: ''
        }
      }
    },
    firebase: {
      posts: firebase.database().ref('posts')
    },
    methods: {
      addPost: function () {
        /*
        this.$firebaseRefs.posts.push(this.post).then(
          (r) => {
            this.post = { title: '' }
          }
        )
         */
        var provider = new firebase.auth.GithubAuthProvider()
        firebase.auth().signInWithPopup(provider).then(function (result) {
          // This gives you a GitHub Access Token. You can use it to access the GitHub API.
          var token = result.credential.accessToken
          console.log(token)
          // The signed-in user info.
          var user = result.user
          console.log(user)
          // ...
        }).catch(function (error) {
          console.warn(error)
        })
      }
    }
  }

</script>

Usamos o método addPost apenas para testar, veja que definimos um provider e o método signInWithPopup para abrir um popoup como este:

Após o usuário logar no github dele, o callback é executado e devemos guardar o token no vuex para uso durante a aplicação.

Obtendo o usuario logado

Use firebase.auth().currentUser para saber se o usuáro está logado. Isso retorna um objeto

Para obter informações do usuário:

var user = firebase.auth().currentUser;
var name, email, photoUrl, uid;

if (user != null) {
  name = user.displayName;
  email = user.email;
  photoUrl = user.photoURL;
  uid = user.uid;  // The user's ID, unique to the Firebase project. Do NOT use
                   // this value to authenticate with your backend server, if
                   // you have one. Use User.getToken() instead.
}

Mais infos aqui

Logout

firebase.auth().signOut().then(function() {
  // Sign-out successful.
}, function(error) {
  // An error happened.
});

Alterando a app para responder ao usuário logado

Configure a variável user para o vuex

No App.vue, no evento created, podemos verificar se o usuário está logado e em caso positivo, usar a action para preencher o state.

<!-- src/App.vue -->
<script>
  import firebase from 'firebase'

  export default {
    name: 'app',
    mounted: function () {
      let t = this
      firebase.auth().onAuthStateChanged(function (user) {
        if (user) {
          t.$store.dispatch('setUser', user)
        } else {
          console.warn('Not logged')
        }
      })
    }
  }

</script>

Como o método onAuthStateChanged da API do firebase nao retorna um promise criamos a variável t para manter o escopo do Vue no callback.

Para configurar o menu, crie uma propriedade computada user

<script>
  import firebase from 'firebase'

  export default {
    name: 'app',
    computed: {
      user () {
        return this.$store.getters.user
      }
    },
    mounted: function () {
      let t = this
      firebase.auth().onAuthStateChanged(function (user) {
        if (user) {
          t.$store.dispatch('setUser', user)
        } else {
          console.warn('Not logged')
        }
      })
    }
  }

</script>

E a use no html do menu

<template v-if="user">
    <a class="nav-item is-tab">
  Posts
</a>
    <a class="nav-item is-tab">
  Comments
</a>
  </template>
  <template v-else>
    <a class="nav-item is-tab">
  <router-link to="/login">Login</router-link>
</a>
  </template>

Ou os itens Posts e Comments serão vistsos, ou o link Login

Tela de login

Crie o componente Login.vue

<template>
  <div>
    <h2> Login </h2>
    <div class="has-text-centered">
      <a class="button is-large" @click="loginGitHub">
    <span class="icon is-medium">
      <i class="fa fa-github"></i>
    </span>
    <span>GitHub</span>
  </a>
    </div>
  </div>
</template>

<script>
  import firebase from 'firebase'

  export default {
    name: 'Login',
    methods: {
      loginGitHub: function () {
        var provider = new firebase.auth.GithubAuthProvider()
        var t = this
        firebase.auth().signInWithPopup(provider).then(function (result) {
          var user = result.user
          t.$store.dispatch('setUser', user)
          t.$router.push('/')
        }).catch(function (error) {
          console.warn(error)
        })
      }
    }
  }

</script>

Logout

Adicione o botão de logout na App.vue

<a class="nav-item is-tab" @click="logout">
 Logout
</a>

e o método:

    methods: {
      logout: function () {
        let t = this
        firebase.auth().signOut().then(function () {
          t.$store.dispatch('setUser', null)
        }, function (error) {
          console.warn(error)
        })
      }
    }

Regras de escrita no BD

Através da regra abaixo configuramos que o qualquer pessoa pode ler os posts, e somente pessoas logadas podem gravar dados.

{
  "rules": {
    "posts": {
     ".read": true,
      "$uid": {
        ".write": "newData.exists() || $uid === auth.uid"
      }
    }
  }
}

Adicionando um Post

Após configurar o App.vue para o menu ir para /addPost (router tb), crie o arquivo components/AddPost.vue com o seguinte código

<template>
  <div>
    <h2> Adicionar um Post </h2>
    <div class="field">
      <label class="label">Título</label>
      <p class="control">
        <input class="input" type="text" placeholder="Título" v-model="post.title">
      </p>
    </div>
    <div class="field">
      <label class="label">Mensagem</label>
      <p class="control">
        <textarea class="textarea" placeholder="" v-model="post.message"></textarea>
      </p>
    </div>
    <div class="field is-grouped" v-if="user">
      <p class="control">
        <button class="button is-primary" @click="addPost">Enviar</button>
      </p>
    </div>
  </div>
</template>

<script>
  import firebase from 'firebase'

  export default {
    name: 'AddPost',
    data () {
      return {
        post: {
          title: '',
          message: ''
        }
      }
    },
    computed: {
      user () {
        return this.$store.getters.user
      }
    },
    firebase: {
      posts: firebase.database().ref('posts')
    },
    methods: {
      addPost: function () {
        this.post.uid = firebase.auth().currentUser.uid

        if (this.post.title === '' || this.post.message === '') {
          return false
        }

        this.$firebaseRefs.posts.push(this.post).then(
          (r) => {
            this.post = { title: '', message: '', uid: '' }
            this.$router.push('/')
          },
          (e) => {
            console.warn(e)
          }
        )
      }
    }
  }

</script>

Vendo posts

Renomear Hello para Home:

<template>
  <div>
    <div v-for="post in posts">
      <div class="card">
        <div class="card-content">
          <div class="media">
            <div class="media-content">
              <p class="title is-4">{{post.title}}</p>
              <p class="subtitle is-6">by user</p>
            </div>
          </div>

          <div class="content">
            {{post.message}}
            <br>
            <small>date</small>
          </div>
        </div>
        <footer class="card-footer" v-if="post.uid===user.uid">
          <a class="card-footer-item" @click="editPost">Editar</a>
          <a class="card-footer-item" @click="deletePost">Remover</a>
        </footer>
      </div>
      <br/>
    </div>


  </div>
</template>

<script>
  import firebase from 'firebase'

  export default {
    name: 'home',
    data() {
      return {
        post: {
          title: ''
        }
      }
    },
    computed: {
      user() {
        return this.$store.getters.user
      }
    },
    firebase: {
      posts: firebase.database().ref('posts')
    },
    methods: {
      addPost: function () {
        this.post.uid = firebase.auth().currentUser.uid
        this.$firebaseRefs.posts.push(this.post).then(
          (r) => {
            this.post = { title: '', uid: '' }
          },
          (e) => {
            console.warn(e)
          }
        )
      },
      deletePost() {
        console.log('delete')
      },
      editPost() {
        console.log('edit')
      }

    }
  }

</script>

<style scoped>

</style>

Gravando usuários no login

//src/components/Login.vue
    loginGitHub: function () {
      var provider = new firebase.auth.GithubAuthProvider()
      var t = this
      firebase.auth().signInWithPopup(provider).then(function (result) {
        var user = result.user
        t.$store.dispatch('setUser', user)

        firebase.database().ref('users/' + user.uid).set({
          name: user.displayName,
          email: user.email,
          photoUrl: user.photoURL
        })

        t.$router.push('/')
      }).catch(function (error) {
        console.warn(error)
      })
    }

Regras do firebase:

{
  "rules": {
    "posts": {
     ".read": true,
      "$uid": {
        ".write": "newData.exists() || $uid === auth.uid"
      }
    },
    "users": {
        "$uid": {
        ".write": "newData.exists() || $uid === auth.uid",
          ".read": "true"
      }
    }
  }
}

Recuperando posts e respectivos usuários

Alterar no Home a forma como os posts são carregados:

    mounted() {
      let t = this;
      firebase.database().ref('posts').on('child_added', function (data) {
        let post = data.val()
        firebase.database().ref('/users/' + post.uid).once('value').then(function (snapshot) {
          post.user = snapshot.val()
          t.posts.push(post)
          console.log(post)
        })
      })
    },

About

Vue book example project

License:Apache License 2.0


Languages

Language:JavaScript 72.7%Language:Vue 25.7%Language:HTML 1.7%