Skip to content

Commit

Permalink
Sign Up View
Browse files Browse the repository at this point in the history
  • Loading branch information
vijayee committed Feb 17, 2017
1 parent 7d1db62 commit c0679e1
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 53 deletions.
1 change: 1 addition & 0 deletions frontend/simple/js/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
import {HapiBackend} from './hapi'

export default new HapiBackend()

13 changes: 13 additions & 0 deletions frontend/simple/js/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,16 @@ export function saveSettings (state: Object): Promise<*> {
export function loadSettings (): Promise<Object> {
return appSettings.getItem('testUser')
}
// =======================
// Store login information
// =======================
const loginInfo = localforage.createInstance({
name: 'Group Income',
storeName: 'Login Information'
})
export function storeLogin (identity: string, hash: string): Promise<Object> {
return loginInfo.setItem(identity, hash)
}
export function retrieveLogin (identity: string): Promise<Object> {
return loginInfo.getItem(identity)
}
8 changes: 7 additions & 1 deletion frontend/simple/js/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ const state = {
currentGroupId: null,
contracts: {}, // contracts we've successfully subscribed to
whitelist: [], // contracts we're expecting to subscribe to
loggedIn: true// TODO: properly handle this
loggedIn: false// TODO: properly handle this
}

// Mutations must be synchronous! Never call these directly!
// http://vuex.vuejs.org/en/mutations.html
const mutations = {
login (state, user) {
state.loggedIn = user
},
logout (state) {
state.loggedIn = false
},
applyAction (state, {action, contractId}) {
action.apply(store, contractId, Vue)
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/simple/views/CreateGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export default {
memberRemovalPercentage: this.memberRemovalPercentage,
incomeProvided: this.incomeProvided,
contributionPrivacy: this.contributionPrivacy,
founderHashKey: 'alsdfjlskdfjaslkfjsldkfj'
founderHashKey: this.$store.state.loggedIn
})
let hash = entry.toHash()
await backend.subscribe(hash)
Expand Down
93 changes: 68 additions & 25 deletions frontend/simple/views/NavBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
<div class="nav-center">
<!-- TODO: use v-for to dynamically generate these? -->
<!--TODO figure out what needs to be done with active classe after upgrade "{activeClass: 'is-active', path: '/new-group'}"-->
<router-link class="nav-item" active-class ="is-active" to="new-group"><i18n>Start a group</i18n></router-link>
<router-link class="nav-item" active-class ="is-active" to="signup"><i18n>New User</i18n></router-link>
<router-link class="nav-item" active-class ="is-active" to="new-group" id="CreateGroup"><i18n>Start a group</i18n></router-link>
<router-link class="nav-item" active-class ="is-active" to="signup" v-show="!$store.state.loggedIn"><i18n>New User</i18n></router-link>
<router-link class="nav-item" active-class ="is-active" to="user"><i18n>Profile</i18n></router-link>
<router-link class="nav-item" active-class ="is-active" to="user-group"><i18n>Group</i18n></router-link>
<router-link class="nav-item" active-class ="is-active" to="pay-group"><i18n>Pay Group</i18n></router-link>
Expand All @@ -39,18 +39,18 @@
them. see: http://bulma.io/documentation/components/nav/ -->
<div class="nav-right">
<span class="nav-item is-tab control">
<router-link class="button is-success" to="/new-user"><i18n>Sign Up</i18n></router-link>
<a href="#" class="button"
v-bind:class="loggedIn ? 'is-danger' : 'is-primary'"
<router-link v-show="!$store.state.loggedIn" class="button is-success" to="/new-user"><i18n>Sign Up</i18n></router-link>
<a href="#" id="LoginBtn" class="button"
v-bind:class="$store.state.loggedIn ? 'is-danger' : 'is-primary'"
@click.prevent="toggleModal"
>
{{ loggedIn ? 'Sign Out' : 'Log In' }}
{{ $store.state.loggedIn ? 'Sign Out' : 'Log In' }}
</a>
</span>
</div>
</div>
</nav>
<div class="modal" ref="modal">
<div class="modal" ref="modal" id="LoginModal">
<div class="modal-background"></div>
<div class="modal-content" style="width: 300px">
<div class="card is-rounded">
Expand All @@ -59,24 +59,27 @@
<i18n>Log In</i18n>
</h1>
<p class="control has-icon">
<input class="input" type="email" placeholder="Email">
<i class="fa fa-envelope"></i>
<input class="input" id="LoginName" name="name" v-model="name" v-validate data-vv-rules="required|regex:^\S+$" placeholder="username" required>
<span class="icon">
<i class="fa fa-user"></i>
</span>
<span v-show="errors.has('name')" class="help is-danger"><i18n>Username cannot contain spaces</i18n></span>
</p>
<p class="control has-icon">
<input class="input" type="password" placeholder="Password">
<i class="fa fa-lock"></i>
</p>
<p class="control">
<label class="checkbox">
<input type="checkbox">
<i18n>Remember me</i18n>
</label>
<input class="input" id="LoginPassword" name="password" v-model="password" v-validate data-vv-rules="required|min:7" placeholder="password" type="password" required>
<span v-show="errors.has('password')" class="help is-danger">Password must be at least 7 characters</span>
<span class="icon is-small">
<i class="fa fa-lock"></i>
</span>
</p>
<span class="help is-danger" id="LoginResponse" v-show="response">{{response}}</span>
<p class="control">
<button class="button is-primary is-medium is-fullwidth"
@click="loginOrLogout"
<button id="LoginButton" class="button is-primary is-medium is-fullwidth"
@click="login" :disabled="errors.any() || !fields.passed()"
>
<i class="fa fa-user"></i>
<span class="icon is-medium">
<i class="fa fa-user"></i>
</span>
<i18n>Login</i18n>
</button>
</p>
Expand All @@ -89,20 +92,60 @@
</template>

<script>
import Vue from 'vue'
import * as db from '../js/database'
import multihash from 'multihashes'
import {HapiNamespace} from '../js/backend/hapi'
var namespace = new HapiNamespace()
export default {
name: 'NavBar',
created: function () {
Vue.events.$on('login', this.toggleModal)
},
methods: {
loginOrLogout () {
if (!this.loggedIn) this.toggleModal()
login: async function () {
try {
let response = await namespace.lookup(this.name)
let identity = JSON.parse(response.text)
let login = await db.retrieveLogin(identity.data.value)
let passHash = multihash.toB58String(multihash.encode(new Buffer(this.password), 'blake2b'))
if (passHash === login) {
this.$store.commit('login', this.name)
this.toggleModal()
console.log('login succeeded')
} else {
this.response = 'Invalid username or password'
console.log('login failed')
}
} catch (ex) {
this.response = 'Invalid username or password'
console.log('login failed')
}
},
logout: function () {
this.$store.commit('logout')
},
toggleModal () {
// https://github.com/oneuijs/You-Dont-Need-jQuery#css--style
this.$refs.modal.classList.toggle('is-active')
if (!this.$refs.modal.classList.contains('is-active') && this.$store.state.loggedIn) {
this.logout()
} else {
this.response = null
this.name = null
this.password = null
this.errors.clear()
// https://github.com/oneuijs/You-Dont-Need-jQuery#css--style
this.$refs.modal.classList.toggle('is-active')
if (this.$route.query.next) {
this.$router.push({path: this.$route.query.next})
}
}
}
},
data () {
return {
loggedIn: false
name: null,
password: null,
response: null
}
}
}
Expand Down
79 changes: 56 additions & 23 deletions frontend/simple/views/SignUp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,27 @@
<div class="box centered" style="max-width:400px">
<h2 class="subtitle">Sign Up</h2>

<a v-on:click="forwardToLogin" style="margin-left: 65%">Already a member?</a>


<p class="control has-icon">
<input class="input" name="name" v-validate data-vv-rules="required|regex:^\S+$" placeholder="username" required>
<input id="name" class="input" name="name" v-model="name" v-on:blur="checkName" v-validate data-vv-rules="required|regex:^\S+$" placeholder="username" required>
<span class="icon is-small">
<i class="fa fa-user"></i>
</span>
<span v-show="errors.has('name')" class="help is-danger">Username cannot contain spaces</span>
<span v-if="errors.has('name')" id="badUsername" class="help is-danger">Username cannot contain spaces</span>
<span v-show="nameAvailable != null && !errors.has('name') " class="help" v-bind:class="[nameAvailable ? 'is-success' : 'is-danger']">{{ nameAvailable ? 'name is available' : 'name is unavailable' }}</span>
</p>
<p class="control has-icon">
<input class="input" name="email" v-validate data-vv-rules="required|email" type="email" placeholder="email" required>
<input class="input" id= "email" name="email" v-model="email" v-validate data-vv-rules="required|email" type="email" placeholder="email" required>
<span class="icon is-small">
<i class="fa fa-envelope"></i>
</span>
<span v-show="errors.has('email')" class="help is-danger">Not an email</span>
<span v-if="errors.has('email')" id="badEmail" class="help is-danger">Not an email</span>
</p>
<p class="control has-icon">
<input class="input" name="password" v-validate data-vv-rules="required|min:7" placeholder="password" type="password" required>
<span v-show="errors.has('password')" class="help is-danger">Password must be at least 7 characters</span>
<input class="input" id="password" name="password" v-model="password" v-validate data-vv-rules="required|min:7" placeholder="password" type="password" required>
<span v-if="errors.has('password')" id="badPassword" class="help is-danger">Password must be at least 7 characters</span>
<span class="icon is-small">
<i class="fa fa-lock"></i>
</span>
Expand Down Expand Up @@ -65,35 +69,64 @@
</style>

<script>
var serialize = require('form-serialize')
var request = require('superagent')
import Vue from 'vue'
import * as Events from '../../../shared/events'
import * as db from '../js/database'
import multihash from 'multihashes'
import {HapiNamespace} from '../js/backend/hapi'
var namespace = new HapiNamespace()
// TODO: fix all this
export default {
name: 'SignUp',
methods: {
submit: function () {
console.log(this.errors)
this.response = ''
request.post(`${process.env.API_URL}/user/`)
.send(serialize(this.$refs.form, {hash: true}))
.end((err, res) => {
this.error = !!err || !res.ok
console.log('this.error', this.error)
this.response = this.error ? res.body.message : (res.text === '' ? 'success' : res.text)
if (!this.error && this.$route.query.next) {
this.login()
this.$router.push({path: this.$route.query.next})
submit: async function () {
try {
let user = new Events.IdentityContract({
attributes: [
{name: 'name', value: this.name},
{name: 'email', value: this.email}
]
})
await namespace.register(this.name, user.toHash())
await db.storeLogin(user.toHash(), multihash.toB58String(multihash.encode(new Buffer(this.password), 'blake2b')))
this.response = 'success'
if (this.$route.query.next) {
setTimeout(() => {
this.$router.push({path: this.$route.query.next})
}, 1000)
}
this.$store.commit('login', this.name)
this.error = false
} catch (ex) {
console.log(ex)
this.response = ex.toString()
this.error = true
}
},
forwardToLogin: function () {
Vue.events.$emit('login')
},
checkName: async function () {
if (this.name && !this.errors.has('name')) {
try {
await namespace.lookup(this.name)
this.nameAvailable = false
} catch (ex) {
this.nameAvailable = true
}
})
return this.nameAvailable
}
}
},
data () {
return {
error: false,
response: '',
formData: {},
untouched: []
nameAvailable: null,
name: null,
password: null,
email: null
}
}
}
Expand Down
65 changes: 62 additions & 3 deletions test/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe('Frontend', function () {
})

it('Should Traverse Log', async function () {
this.timeout(10000)
this.timeout(4000)
await n.goto(page('event-log'))
let prior = await n.evaluate(() => document.getElementById('LogPosition').innerText)
let initial = await n.wait('textarea[name="payload"]')
Expand All @@ -88,11 +88,70 @@ describe('Frontend', function () {
})
})

describe('Sign up Test', function () {
let randInt = (min, max) => Math.floor(Math.random() * (max - min)) + min
let username = `testuser${randInt(0, 10000000)}${randInt(10000000, 20000000)}`
it('Should register User', async function () {
this.timeout(4000)
await n.goto(page('signup'))
let signedup = await n.insert('#name', username)
.insert('#email', `[email protected]`)
.insert('#password', 'testtest')
.click('button[type="submit"]')
.wait(() => !!document.getElementById('serverMsg').innerText)
.evaluate(() => document.getElementById('serverMsg').innerText)
should(signedup).equal('success')
/*
await n.insert('#name')
.insert('#email')
.insert('#password')
.wait(() => !document.getElementById('name').innerText &&
!document.getElementById('email').innerText &&
!document.getElementById('password').innerText)
*/
})
it('Test Logout and Login', async function () {
this.timeout(4000)
let response = await n.click('#LoginBtn')
.wait(() => document.getElementById('LoginBtn').classList.contains('is-primary'))
.click('#LoginBtn')
.wait(() => document.getElementById('LoginModal').classList.contains('is-active'))
.insert('#LoginName', username)
.insert('#LoginPassword', 'testtest')
.click('#LoginButton')
.wait(() => !!document.getElementById('LoginResponse'))
.evaluate(() => document.getElementById('LoginResponse').innerText)
should(response).not.equal('Invalid username or password')
await n.wait(10000)
})
/* There appears to be a bug in nightmare that causes the insert and type commands to enter old data into the field if the
insert or type commands or used more than once on the same field. This concatenates the old values to the new.
This occurs regardless of whether you clear the field or not. Until its fixed skip this validation test
*/
it.skip('Test Validation', async function () {
this.timeout(40000)
await n.goto(page('signup'))
let badUsername = 't e s t'
let badEmail = `@fail`
// let badPassword = `789`// six is so afraid
let denied = await n.insert('#name', badUsername)
.insert('#email', badEmail)
.insert('#password', ' ')
.evaluate(() => document.querySelector('button[type="submit"]').disabled)
should(denied).equal(true)
let usernameMsg = await n.evaluate(() => !!document.getElementById('badUsername'))
should(usernameMsg).equal(true)
let emailMsg = await n.evaluate(() => !!document.getElementById('badEmail'))
should(emailMsg).equal(true)
let passwordMsg = await n.evaluate(() => !!document.getElementById('badPassword'))
should(passwordMsg).equal(true)
})
})

describe('Group Creation Test', function () {
it('Should create a group', async function () {
this.timeout(10000)
await n.goto(page('new-group'))
.should.finally.containEql({ code: 200, url: page('new-group') })
await n.click('#CreateGroup')
let created = await n.insert('input[name="groupName"]', 'Test Group')
.insert('textarea[name="sharedValues"]', 'Testing this software')
.insert('input[name="groupName"]', 'Test Group')
Expand Down

0 comments on commit c0679e1

Please sign in to comment.