Buat Aplikasi Seperti Secreto menggunakan Vue.js
Pada kesempatan kali ini saya akan berbagi tutorial membuat aplikasi seperti Secreto.site yang sempat viral beberapa waktu lalu. Aplikasi ini mengirimkan pesan tanpa nama yang dimana aplikasi ini memiliki tagline “The anonymous messaging app.” Artikel ini juga pernah tayang di Medium saya.
Menyiapkan Project
Pertama sudah install node.js, lalu install package vue-cli
npm install -g vue-cli
Selanjutnya gunakan vuejs-template webpack yang sudah tersedia disini
vue init webpack <nama-project>
Ikuti perintah pada command line dan gunakan juga vue-router pada saat penginstallan. Jika sudah selesai masuk ke direktori projectmu, lalu install dependency pada boiler plate yang kita gunakan.
npm install
Jika sudah, coba jalankan aplikasi dengan perintah
npm run dev
Secara default aplikasi tersebut akan berjalan di port 8080
localhost:8080
Kamu akan melihat halaman seperti ini pada browser kamu:
Langkah selanjutnya adalah kita akan menginstal beberapa dependecy lain yang akan digunakan dalam project ini.
install buefy (sebagai templatenya)
npm install buefy --save
install firebase (Untuk menggunakan firebase storage)
npm install firebase --save
install vue-google-signin-button (Untuk login menggunakan Akun Google)
npm install vue-google-signin-button --save
install vue-clipboards (Untuk mengcopy alamat URL)
npm install vue-clipboards --save
install vue-social-sharing (Untuk share link profil)
npm install vue-social-sharing --save
Jika semua dependencys sudah terinstall semua, langkah selanjutnya adalah mulai coding.
Mengedit index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Katakan Saja - Platform untuk mengatakan sejujurnya</title>
<meta name="description" content="Platform untuk mengatakan sejujurnya">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css" integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg"
crossorigin="anonymous">
<script type="text/javascript" src="https://apis.google.com/js/api:client.js"></script>
<meta property="og:image" content="https://image.prntscr.com/image/_rior17fSsyzhx9sftb-DA.png">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
Didalam source code tesebut ada script client.js google yang digunakan untuk sigin with google dan font awesome sebagai iconnya.
Langkah selanjutnya buka main.js pada direktori /src/main.js dan edit sebagai berikut:
// 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 buefy from 'buefy'
import 'buefy/lib/buefy.css'
import GSignInButton from 'vue-google-signin-button'
import VueClipboards from 'vue-clipboards'
import SocialSharing from 'vue-social-sharing'
Vue.config.productionTip = false
Vue.use(buefy, {
defaultIconPack: 'fas'
})
Vue.use(GSignInButton)
Vue.use(VueClipboards)
Vue.use(SocialSharing)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
Pada buefy (line 13–15) gunakan default icon pack fonts awesome. File main js ini nantinya akan menginit vue js dengan config yang sudah dibuat tersebut dan me-return template “
Langkah Selanjutnya adalah edit App.vue pada /src/App.vue
<template>
<div id="app">
<nav id="navbar" ref="navbar" class="navbar is-fixed-top has-shadow is-warning" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<button class="button is-warning"><i class="fas fa-leaf"></i></button><p style="color: white">Katakan Saja</p>
</a>
<button class="button navbar-burger is-warning is-no-outline" data-target="navMenu" @click="spanNavigation()">
<span></span>
<span></span>
<span></span>
</button>
</div>
<div class="navbar-menu is-warning" :class="navbarSpan?'is-active':''" id="navMenu">
<div class="navbar-end">
<router-link class="navbar-item" tag="a" :to="'/daftar'"><p style="color: white">Daftar</p></router-link>
</div>
</div>
</nav>
<router-view/>
<div class="navbar is-fixed-bottom is-light" style="align-item:center">
<div class="is-footer-content"><router-link tag="a" :to="'/contact'"><p>Kontak</p></router-link></div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
navbarSpan: false
}
},
methods: {
spanNavigation () {
this.navbarSpan = !this.navbarSpan
}
}
}
</script>
<style>
@import url('https://fonts.googleapis.com/css?family=Roboto:300');
#app {
font-family: 'Roboto', sans-serif;
margin-top: 60px;
}
.is-footer-content{
font-size: 10pt;
display: flex;
align-items:center;
width: 100%;
justify-content: center;
}
</style>
pada komponen App.vue ini kita akan membuat layout aplikasi, yatu navbar, content-loader dan footer
Dari layout aplikasi yang terdiri dari navbar, vue-router dan footer.
Aplikasi ini terdiri dari 4 components: Home, Register, User, Contact.
Langkah selanjutnya buat component Home.vue di direktori /src/components
<template>
<div style="padding-bottom:60px">
<section class="section">
<div class="container">
<div class="has-text-centered">
<button class="button is-warning" style="margin-bottom:30px"><i class="fas fa-leaf unguiconcolor"></i></button>
<h1 class="title">Katakan Saja</h1>
<h3> Platform Katakan Saja <b><i>Kepada Temanmu</i></b></h3>
<br>
<div class="columns">
<div class="column is-3"></div>
<div class="column is-6">
<div class="box">
<h2 class="title">
Katakan Saja Kepada Temanmu seperti Secreto
</h2>
<hr>
<router-link class="button is-info" tag="a" :to="'/daftar'">Daftar</router-link>
</div>
</div>
<div class="column is-3"></div>
</div>
</div>
</div>
</section>
</div>
</template>
<script>
import * as firebase from 'firebase'
import 'firebase/firestore'
const config = {
apiKey: '', //masukan apikey
authDomain: '', //masukan auth domain
databaseURL: '', //masukan db url
projectId: '', //masukan id project
storageBucket: '', //masukan bucket storage
messagingSenderId: '' //masukan sender ID
}
firebase.initializeApp(config)
const db = firebase.firestore()
export default {
name: 'Home',
data () {
return {
Messages: [],
kata2mu: null,
loading: true
}
},
methods: {
getAllMessageFromFirebase () {
const vueContext = this
const messageRef = db.collection('katakan-saja') //nama db pada firestore
messageRef.orderBy('timestamp', 'desc').get().then(function (querySnapshot) {
let messageData = []
querySnapshot.forEach(function (doc) {
messageData.push(doc.data())
})
vueContext.Messages = messageData
vueContext.loading = false
})
},
addKatakanToFirebase () {
const vueContext = this
const messageRef = db.collection('katakan-saja') //nama db pada firestore
const timeStamp = firebase.firestore.FieldValue.serverTimestamp()
vueContext.loading = true
messageRef.add({
message: vueContext.kata2mu,
timestamp: timeStamp
})
.then(function (docRef) {
vueContext.getAllMessageFromFirebase()
})
.catch(function (error) {
console.error('Error : ', error)
vueContext.loading = false
vueContext.$snackbar.open({
duration: 5000,
message: 'Kata-Katamu gagal dikirim!',
type: 'is-danger',
position: 'is-bottom-left',
actionText: 'Kembalikan',
queue: false,
onAction: () => {
vueContext.addKatakanToFirebase()
}
})
})
}
},
mounted () {
this.getAllMessageFromFirebase()
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.is-no-outline{
border-radius: 0px;
border:0px;
}
.is-footer-content{
font-size: 10pt;
display: flex;
align-items:center;
width: 100%;
justify-content: center;
}
.katakan{
font-size:11pt;
font-style: italic;
}
.is-roundedfull{
width: 40px;
height: 40px;
border-radius:50%;
}
.box2{
border: 1px solid rgb(247, 245, 245);
background:#f5f5f5;
padding: 20px 20px 20px 20px;
border-radius:5px 5px 5px 5px;
}
.messages{
margin-top: 20px;
margin-bottom: 20px;
}
.unguiconcolor {color:#ffffff;}
</style>
buat component Register.vue di direktori /src/components
<template>
<div>
<section class="section">
<div class="container">
<div class="columns">
<div class="column"></div>
<div class="column has-text-centered">
<div class="box">
<i class="fas fa-user-circle fa-5x" v-if="!isRegistered"></i>
<div v-if="isRegistered">
<img :src="profile.Paa" :alt="profile.U3" class="is-have-border">
<h1></h1>
</div>
<hr>
<h1></h1>
</div>
<div v-if="isRegistered">
<input type="text" class="input is-small" v-model="url">
<br><br>
<router-link class="button is-info" tag="a" :to="'/user/'+ profile.Eea">Lihat Halaman Saya</router-link>
<button class="button is-info" v-clipboard="url" @click="toastCopy">
<i class="fas fa-copy"> Salin URL</i>
</button>
</div>
<br>
<g-signin-button
:params="googleSignInParams"
@success="onSignInSuccess"
@error="onSignInError">
<i class="fab fa-google"> </i>
</g-signin-button>
<br><br>
Bagikan :
<social-sharing :url="url" title="Katakan Saja - Platform untuk mengatakan sejujurnya"
description="Platform untuk mengatakan sejujurnya." inline-template>
<div>
<network network="facebook">
<button class="button is-info">
<i class="fab fa-facebook"></i>
</button>
</network>
<network network="twitter">
<button class="button is-info">
<i class="fab fa-twitter"></i>
</button>
</network>
<network network="whatsapp">
<button class="button is-info">
<i class="fab fa-whatsapp"></i>
</button>
</network>
</div>
</social-sharing><br>
</div>
<div class="column"></div>
</div>
</div>
</section>
</div>
</template>
<script>
import * as firebase from 'firebase'
import 'firebase/firestore'
const db = firebase.firestore()
export default {
name: 'register',
data () {
return {
/**
* The Auth2 parameters, as seen on
* https://developers.google.com/identity/sign-in/web/reference#gapiauth2initparams.
* As the very least, a valid client_id must present.
* @type {Object}
*/
googleSignInParams: {
client_id: '' //Dapatkan IDnya dari https://developers.google.com/identity/sign-in/web/sign-in
},
profile: null,
isRegistered: false,
message: 'Daftar untuk menampung kata-kata dari teman-teman mu!',
url: null,
btnGoogle: 'Daftar dengan Google'
}
},
methods: {
onSignInSuccess (googleUser) {
const vueInstance = this
const messageRef = db.collection('user') //nama db untuk menyimpan user
const rawProfile = googleUser.getBasicProfile() // etc etc
const uId = googleUser.getId()
messageRef.doc(uId).set({
pictures: rawProfile.Paa,
email: rawProfile.U3,
name: rawProfile.ig
})
.then(function (docRef) {
vueInstance.profile = rawProfile
vueInstance.isRegistered = true
vueInstance.message = 'Halaman Katakan Saja telah dibuat, Bagikan ke teman anda Sekarang!'
vueInstance.url = 'https://katakan.azurewebsites.net/user/' + uId
localStorage.setItem('profile', JSON.stringify(rawProfile))
})
.catch(function (error) {
vueInstance.$snackbar.open(error)
})
},
onSignInError (error) {
this.$snackbar.open(error)
},
toastCopy () {
this.$toast.open({
position: 'is-bottom',
message: `copied`
})
}
},
mounted () {
const vueInstance = this
if (localStorage.getItem('profile')) {
const data = JSON.parse(localStorage.getItem('profile'))
vueInstance.profile = data
vueInstance.isRegistered = true
vueInstance.message = 'Halaman Katakan Saja telah dibuat, Bagikan ke teman anda Sekarang!'
vueInstance.url = 'https://katakan.azurewebsites.net/user/' + data.Eea
vueInstance.btnGoogle = 'Pake Akun Lain'
}
}
}
</script>
<style scoped>
.g-signin-button {
/* This is where you control how the button looks. Be creative! */
display: inline-block;
padding: 4px 8px;
border-radius: 3px;
background-color: #3c82f7;
color: #fff;
box-shadow: 0 3px 0 #0f69ff;
cursor:pointer;
}
.is-have-border{
border-radius: 50%;
box-shadow: 2px 1px 0 #0000005b;
}
</style>
buat component User.vue di direktori /src/components
<template>
<div style="padding-bottom:60px">
<section class="section">
<div class="container">
<div class="has-text-centered">
<div class="columns">
<div class="column is-3"></div>
<div class="column is-6">
<div v-if="profile">
<img :src="profile.pictures" :alt="profile.U3" class="is-have-border">
<h1>KIRIM KATA-KATA SECARA RAHASIA KEPADA</h1>
<h1><b></b></h1>
<br>
</div>
<li>Tenang, ga bakal tahu siapa yang ngirim kata-kata</li>
<textarea class="textarea" placeholder="Tulis Kata-Kata Rahasiamu!" v-model="kata2mu"></textarea>
<br>
<button class="button is-info is-small" @click="addKatakanToFirebase">Katakan Pada </button>
<!-- content here -->
<div style="margin-top:30px">
<div class="has-text-centered" :class="!loading?'is-hidden':''">
<i class="fas fa-spinner fa-spin"></i>
</div>
<div v-for="(key, index) in Messages" :key="index" class="messages">
<article class="media">
<div class="media-left">
<button class="button is-roundedfull is-warning"> <i class="fas fa-envelope"></i></button>
</div>
<div class="box2 media-content">
<div class="content katakan">
<br>
<div style="color:gray; font-size:7pt !important" class="has-text-right"></div>
</div>
</div>
</article>
</div>
<div class="has-text-centered katakan" v-if="Messages.length === 0" :class="loading?'is-hidden':''">
Belum ada data <i class="fa fa-frown"></i>
</div>
</div>
</div>
<div class="column is-3"></div>
</div>
</div>
</div>
</section>
</div>
</template>
<script>
import * as firebase from 'firebase'
import 'firebase/firestore'
const db = firebase.firestore()
export default {
name: 'Home',
data () {
return {
Messages: [],
kata2mu: null,
loading: true,
profile: null
}
},
methods: {
getAllMessageFromFirebase () {
const vueContext = this
const uId = vueContext.$route.params.uId
const messageRef = db.collection(uId)
messageRef.orderBy('timestamp', 'desc').get().then(function (querySnapshot) {
let messageData = []
querySnapshot.forEach(function (doc) {
messageData.push(doc.data())
})
vueContext.Messages = messageData
vueContext.loading = false
})
.catch(function (error) {
vueContext.loading = false
vueContext.$snackbar.open(error)
})
},
getProfile () {
const vueContext = this
const uId = vueContext.$route.params.uId
const messageRef = db.collection('user')
messageRef.doc(uId).get()
.then(function (doc) {
if (doc.exists) {
vueContext.profile = doc.data()
} else {
console.log('No such document!')
}
})
.catch(function (error) {
vueContext.$snackbar.open(error)
})
},
addKatakanToFirebase () {
const vueContext = this
const uId = vueContext.$route.params.uId
const messageRef = db.collection(uId)
const timeStamp = firebase.firestore.FieldValue.serverTimestamp()
vueContext.loading = true
messageRef.add({
message: vueContext.kata2mu,
timestamp: timeStamp
})
.then(function (docRef) {
vueContext.getAllMessageFromFirebase()
})
.catch(function (error) {
console.error('Error : ', error)
vueContext.loading = false
vueContext.$snackbar.open({
duration: 5000,
message: 'Kata-Katamu gagal dikirim!',
type: 'is-danger',
position: 'is-bottom-left',
actionText: 'Kembalikan',
queue: false,
onAction: () => {
vueContext.addKatakanToFirebase()
}
})
})
}
},
mounted () {
this.getAllMessageFromFirebase()
this.getProfile()
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.is-no-outline{
border-radius: 0px;
border:0px;
}
.is-footer-content{
font-size: 10pt;
display: flex;
align-items:center;
width: 100%;
justify-content: center;
}
.katakan{
font-size:11pt;
font-style: italic;
}
.is-roundedfull{
width: 40px;
height: 40px;
border-radius:50%;
}
.box2{
border: 1px solid rgb(247, 245, 245);
background:rgb(252, 252, 252);
padding: 20px 20px 5px 20px;
border-radius:0px 10px 10px 15px;
}
.messages{
margin-top: 20px;
margin-bottom: 20px;
}
.is-have-border{
border-radius: 50%;
box-shadow: 2px 1px 0 #0000005b;
}
</style>
buat component Contact.vue di direktori /src/components
<template>
<div style="padding-bottom:60px">
<section class="section">
<div class="container">
<div class="has-text-centered">
<div class="columns">
<div class="column is-3"></div>
<div class="column is-6">
<div class="box">
<h1>Contact:</h1><br>
<p>Aplikasi ini hanya untuk iseng belaka <br>
Dibuat menggunakan Vue.JS, Buefy, dan Firestore <br>
brianetlab@gmail.com<br>
https://brianrakhmat.github.io
</p>
</div>
</div>
<div class="column is-3"></div>
</div>
</div>
</div>
</section>
</div>
</template>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.is-no-outline{
border-radius: 0px;
border:0px;
}
.is-footer-content{
font-size: 10pt;
display: flex;
align-items:center;
width: 100%;
justify-content: center;
}
.pisuhan{
font-size:11pt;
font-style: italic;
}
.is-roundedfull{
width: 40px;
height: 40px;
border-radius:50%;
}
.box2{
border: 1px solid rgb(247, 245, 245);
background:rgb(252, 252, 252);
padding: 20px 20px 5px 20px;
border-radius:0px 10px 10px 15px;
}
.messages{
margin-top: 20px;
margin-bottom: 20px;
}
.is-have-border{
border-radius: 50%;
box-shadow: 2px 1px 0 #0000005b;
}
</style>
Setelah 4 Components tadi dibuat langkah selanjutnya adalah mengatur routingnya edit file index.js pada /src/router/index.js , file tersebut berisi rout aplikasi. Konten yang kamu buat akan dirender pada komponen App.vue bagian vue-router.
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import Register from '@/components/Register'
import User from '@/components/User'
import Contact from '@/components/Contact'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/daftar',
name: 'Register',
component: Register
},
{
path: '/user/:uId',
name: 'User',
component: User
},
{
path: '/contact',
name: 'Contact',
component: Contact
}
]
})
Sampai disini kamu sudah bisa menjalankan aplikasi tersebut dengan menjalankan perintah
npm run dev
dan ketikan http://localhost:8080 pada browser
Langkah selanjutnya saya akan menjelaskan cara mendeploy aplikasi ini ke dalam azure websites pada tutorial lainnya.
Demo aplikasi: https://katakan.azurewebsites.net
Thanks for tutorial vuejs mas Iqbal
Leave a Comment