Skip to content

Commit

Permalink
authentication, alerts, header, search
Browse files Browse the repository at this point in the history
  • Loading branch information
Swimburger committed May 22, 2016
1 parent e23a084 commit d0b2f51
Show file tree
Hide file tree
Showing 15 changed files with 494 additions and 41 deletions.
2 changes: 1 addition & 1 deletion firebase.json
@@ -1,5 +1,5 @@
{
"firebase": "gkeep-vueifire2",
"firebase": "gkeep-vueifire3",
"public": "dist",
"ignore": [
"firebase.json",
Expand Down
2 changes: 1 addition & 1 deletion index.html
Expand Up @@ -12,7 +12,7 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
</head>
<body>
<a href="https://github.com/SNiels/gkeep-vueifire"><img style="position: absolute; top: 0; right: 0; border: 0; z-index: 2;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
<a href="https://github.com/SNiels/gkeep-vueifire"><img style="position: fixed; top: 0; left: 0; border: 0; z-index: 2;" src="https://camo.githubusercontent.com/82b228a3648bf44fc1163ef44c62fcc60081495e/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f6c6566745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_left_red_aa0000.png"></a>
<app></app>
<footer>
Made by <a href="http://www.sniels.com">Niels Swimberghe</a> with a lot of <i class="fa fa-coffee"></i> and <i class="fa fa-heart"></i>
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -14,7 +14,8 @@
"babel-runtime": "^5.8.0",
"firebase": "^2.4.2",
"masonry-layout": "^4.0.0",
"vue": "^1.0.18"
"vue": "^1.0.18",
"vue-router": "^0.7.13"
},
"devDependencies": {
"babel-core": "^6.0.0",
Expand Down
37 changes: 23 additions & 14 deletions src/App.vue
@@ -1,28 +1,32 @@
<template>
<div>
<create-note-form></create-note-form>
<notes></notes>
<update-modal :note.sync="selectedNote"></update-modal>
<header-bar></header-bar>
<alerts :alerts="alerts"></alerts>
<router-view></router-view>
</div>
</template>
<script>
import Notes from './components/notes/Index'
import CreateNoteForm from './components/notes/Create'
import UpdateModal from './components/notes/UpdateModal'
import Alerts from './components/Alerts'
import HeaderBar from './components/Header'
export default {
components: {
Notes,
CreateNoteForm,
UpdateModal
Alerts,
HeaderBar
},
data () {
return {
selectedNote: null
alerts: []
}
},
events: {
'note.selected': function (note) {
this.selectedNote = note
'alert': function (alert) {
this.alerts.push(alert)
setTimeout(() => {
this.alerts.$remove(alert)
}, alert.duration || 1500)
},
'search': function (searchText) {
this.$broadcast('search', searchText) // send the event downwards to children
}
}
}
Expand All @@ -36,14 +40,14 @@ export default {
html{
position: relative;
min-height: 100%;
font-family: sans-serif;
font-family: 'Roboto', sans-serif;
}
body{
background: #eee;
min-height: 100%;
}
body > div{
padding: 16px 16px 50px;
padding: 50px 16px;
min-height: 100vh;
background: #eee;
position: relative;
Expand Down Expand Up @@ -71,4 +75,9 @@ footer iframe{
position: relative;
top: 4px;
}
.clearfix:after {
content: "";
display: table;
clear: both;
}
</style>
48 changes: 48 additions & 0 deletions src/components/Alerts.vue
@@ -0,0 +1,48 @@
<template>
<div class="alerts">
<div v-for="alert in alerts" v-bind:class="alert.type" transition="expand">
<p>
{{alert.message}}
</p>
</div>
</div>
</template>
<script>
export default {
props: ['alerts']
}
</script>
<style>
.alerts{
position: fixed;
top: 50px;
left: 0;
right: 0;
z-index: 1;
}
.alerts div{
background: #bbb;
overflow: hidden;
}
.alerts div.error{
background: #e03c3c;
color: #fff;
}
.alerts div.success{
background: #41b883;
color: #fff;
}
.alerts p{
width: 480px;
margin: 0 auto;
padding: 4px;
text-align: center;
}
.expand-transition{
max-height: 200px; /* height 0 -> auto is not animatable, so use max-height 0 -> large height */
transition: max-height 1s ease;
}
.expand-enter, .expand-leave{
max-height: 0;
}
</style>
119 changes: 119 additions & 0 deletions src/components/Header.vue
@@ -0,0 +1,119 @@
<template>
<header v-if="user">
<input placeholder="Search" v-model="searchQuery" debounce="500">
<div>
<span>{{user.userTitle}}</span>
<img :src="user.imageUrl" alt="{{user.userTitle}}"/>
<i class="fa fa-sign-out" aria-hidden="true" v-on:click="signOut"></i>
</div>
</header>
</template>
<script>
import authRepository from 'src/data/AuthRepository'
export default {
data () {
return {
user: null,
searchQuery: ''
}
},
watch: {
'searchQuery': function () {
this.$dispatch('search', this.searchQuery)
}
},
methods: {
processUser (authed) {
switch (authed.provider) {
case 'password':
this.user = {
userTitle: authed.password.email,
imageUrl: authed.password.profileImageURL
}
break
default:
this.user = {
userTitle: authed[authed.provider].displayName,
imageUrl: authed[authed.provider].profileImageURL
}
break
}
},
signOut () {
authRepository.signOut()
this.$router.go('auth')
}
},
ready () {
authRepository.detachFirebaseListeners()
authRepository.on('authed', () => {
this.processUser(authRepository.getAuth())
})
authRepository.on('unauthed', () => {
this.user = null
})
this.user = authRepository.getAuth()
authRepository.attachFirebaseListeners()
}
}
</script>
<style>
header{
position: fixed;
left: 0;
top: 0;
right: 0;
z-index: 1;
height: 50px;
background: #333;
padding: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,.4);
}
header input {
display: block;
width: 480px;
margin: 0 auto;
height: 30px;
border: none;
padding: 0 16px;
border-radius: 2px;
}
header span {
padding: 15px;
color: #fff;
position: absolute;
right: 95px;
top: 1px;
}
header img {
width: 35px;
height: 35px;
border-radius: 20px;
position: absolute;
right: 60px;
top: 8px;
}
header i.fa {
position: absolute;
display: block;
color: #fff;
right: 15px;
top: 13px;
font-size: 25px;
cursor: pointer;
}
@media screen and (max-width: 1200px) {
header span{
display: none;
}
}
@media screen and (max-width: 720px) {
header input{
width: calc(100% - 64px);
margin: 0 0 0 16px;
}
header span, header img {
display: none;
}
}
</style>
5 changes: 3 additions & 2 deletions src/components/notes/Create.vue
Expand Up @@ -20,9 +20,10 @@ export default {
createNote () {
if (this.title.trim() || this.content.trim()) {
noteRepository.create({title: this.title, content: this.content}, (err) => {
if (err) throw err // TODO: inform the user
if (err) return this.$dispatch('alert', {type: 'error', message: 'Failed to create note'})
this.title = ''
this.content = ''
this.$dispatch('alert', {type: 'success', message: 'Note was successfully created'})
})
}
}
Expand All @@ -34,7 +35,7 @@ form.create-note{
position: relative;
width: 480px;
max-width: 100%;
margin: 0 auto 15px;
margin: 30px auto;
background: #fff;
padding: 15px;
border-radius: 2px;
Expand Down
28 changes: 23 additions & 5 deletions src/components/notes/Index.vue
@@ -1,7 +1,7 @@
<template>
<div class="notes" v-el:notes>
<note
v-for="note in notes"
v-for="note in filteredNotes"
:note="note"
v-on:click="selectNote(note)"
>
Expand All @@ -18,7 +18,8 @@ export default {
},
data () {
return {
notes: []
notes: [],
searchQuery: ''
}
},
methods: {
Expand All @@ -28,16 +29,32 @@ export default {
this.$dispatch('note.selected', {key, title, content})
}
},
computed: {
filteredNotes () {
return this.notes.filter((note) => {
if (this.searchQuery) return (note.title.indexOf(this.searchQuery) !== -1 || note.content.indexOf(this.searchQuery) !== -1)
return true
})
}
},
watch: {
'notes': { // watch the notes array for changes
'filteredNotes': { // watch the notes array for changes
handler () {
this.masonry.reloadItems()
this.masonry.layout()
this.$nextTick(() => {
this.masonry.reloadItems()
this.masonry.layout()
})
},
deep: true // we also want to watch changed inside individual notes
}
},
events: {
'search': function (searchQuery) {
this.searchQuery = searchQuery
}
},
ready () {
noteRepository.detachFirebaseListeners()
this.masonry = new Masonry(this.$els.notes, {
itemSelector: '.note',
columnWidth: 240,
Expand All @@ -56,6 +73,7 @@ export default {
let note = noteRepository.find(this.notes, key) // get specific note from the notes in our VM by key
this.notes.$remove(note) // remove note from notes array
})
noteRepository.attachFirebaseListeners()
}
}
</script>
Expand Down
2 changes: 1 addition & 1 deletion src/components/notes/Note.vue
Expand Up @@ -26,7 +26,7 @@ export default {
methods: {
remove () {
noteRepository.remove(this.note, (err) => {
if (err) throw err // TODO: inform the user
if (err) return this.$dispatch('alert', {type: 'error', message: 'Failed to remove note'})
})
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/components/notes/UpdateModal.vue
Expand Up @@ -18,14 +18,15 @@ export default {
methods: {
remove () {
noteRepository.remove(this.note, (err) => {
if (err) throw err // TODO: inform the user
if (err) return this.$dispatch('alert', {type: 'error', message: 'Failed to remove note'})
this.dismissModal()
})
},
update () {
noteRepository.update(this.note, (err) => {
if (err) throw err // TODO: inform the user
if (err) return this.$dispatch('alert', {type: 'error', message: 'Failed to update note'})
this.dismissModal()
this.$dispatch('alert', {type: 'success', message: 'Note was successfully updated'})
})
},
dismissModal () {
Expand Down

0 comments on commit d0b2f51

Please sign in to comment.