Ind.ie is now Small Technology Foundation.
Commit e50960c5 authored by Aral Balkan's avatar Aral Balkan

Implement client-side of registration form

  * Mocked server call (always successful)
  * Forked vue-password components and added it inline to the project for easier development (it’s a simple component)
parent e1e50a58
<template>
<div>
<form @submit.prevent='submit'>
<form @submit.prevent>
<b-field label='Name'>
<b-input v-model='name' placeholder='Anonymous'></b-input>
......@@ -38,34 +38,42 @@
<header class='modal-card-head'>
<p class='modal-card-title'>Welcome to your Indie Site!</p>
</header>
<section class='modal-card-body'>
<div class='instructions'>
<p>To start, please pick a strong password to protect yourself.</p>
<p>Please don’t lose this. Your site will not store your password to protect your privacy and you can’t recover it if you forget it. If possible, generate and store your password using a password manager like <a href='https://1password.com'>1Password</a> or <a href='https://keepass.info'>KeePass</a>.</p>
</div>
<b-field label='Password'>
<div id='password-field' class='control'>
<no-ssr>
<vue-password v-model='password' classes='input' :disabled='isLoading'>
<template slot='password-toggle' slot-scope='props'>
<button
class='button VuePassword__Toggle'
type='button'
@click='props.toggle'
:disabled='isLoading'
>{{ props.type === 'password' ? 'Show' : 'Hide' }}</button>
</template>
</vue-password>
</no-ssr>
<form @submit.prevent='submitPasswordForm'>
<section class='modal-card-body'>
<div class='instructions'>
<p>To start, please pick a strong password to protect yourself.</p>
<p>Please don’t lose this. Your site will not store your password to protect your privacy and you can’t recover it if you forget it. If possible, generate and store your password using a password manager like <a href='https://1password.com'>1Password</a> or <a href='https://keepass.info'>KeePass</a>.</p>
</div>
</b-field>
</section>
<footer class="modal-card-foot">
<button :class='setPasswordButtonClasses'>
<span v-if='registrationIsSuccessful' id='success-icon' class='icon is-medium'><i class='fa fa-check'></i></span>
{{ setPasswordButtonText }}</button>
</footer>
<b-field label='Password'>
<div id='password-field' class='control'>
<no-ssr>
<vue-password
placeholder='Choose a strong password'
v-model='password'
classes='input'
:disabled='isLoading || registrationIsSuccessful'
@strength='validatePasswordStrength'
>
<template slot='password-toggle' slot-scope='props'>
<button
class='button VuePassword__Toggle'
type='button'
@click='props.toggle'
:disabled='isLoading || registrationIsSuccessful'
>{{ props.type === 'password' ? 'Show' : 'Hide' }}</button>
</template>
</vue-password>
</no-ssr>
</div>
</b-field>
</section>
<footer class="modal-card-foot">
<button :class='setPasswordButtonClasses' type='submit' :disabled='!passwordIsStrongEnough || isLoading || registrationIsSuccessful'>
<span v-if='registrationIsSuccessful' id='success-icon' class='icon is-medium'><i class='fa fa-check'></i></span>
{{ setPasswordButtonText }}</button>
</footer>
</form>
</div>
</b-modal>
</div>
......@@ -73,18 +81,22 @@
<script>
import vuePassword from '~/components/vuePassword'
import vueColorCompact from '~/../node_modules/vue-color/src/components/Compact.vue'
export default {
components: {
vueColorCompact
vueColorCompact,
vuePassword
},
computed: {
modalIsActive () { return !this.configured },
setPasswordButtonText () {
return this.registrationIsSuccessful ? "Done" : "Set password"
let text = this.registrationIsSuccessful ? 'Done' : 'Set password'
if (this.isLoading) text = 'Registering'
return text
},
setPasswordButtonClasses () {
......@@ -136,18 +148,45 @@
configured: {
get () { return this.$store.state.configured },
set (value) { this.$store.commit('configured', value) }
},
signedIn: {
get () { return this.$store.state.signedIn },
set (value) { this.$store.commit('signedIn', value) }
}
},
data () {
return {
password: '',
passwordIsStrongEnough: false,
isLoading: false,
registrationIsSuccessful: true
registrationIsSuccessful: false
}
},
methods: {
submit () {
console.log('⚠️ Settings form submit is currently unimplemented.')
// Called when the input in the password form changes with the current strength.
validatePasswordStrength (strength) {
// We will only accept passwords that are “strong” or “very strong” on
// the zxcvbn scale.
this.passwordIsStrongEnough = (strength.score >= 3)
},
submitPasswordForm () {
console.log('⚠️ Settings screen password form submit is currently unimplemented.')
this.isLoading = true
// Mock a successful registration.
setTimeout(() => {
this.isLoading = false
this.registrationIsSuccessful = true
// After the person has had a chance to see that the registration
// was successful, remove the modal dialog.
setTimeout(() => {
this.signedIn = true
this.configured = true
}, 1000);
}, 1250);
}
}
}
......
// Forked from: https://github.com/skegel13/vue-password
<template>
<div class="VuePassword" :title="feedback">
<div class="VuePassword__Input">
<input type="password"
:class="classes"
:value="value"
ref="input"
@blur="emitBlur"
@focus="emitFocus"
@input="updatePassword($event.target.value)"
v-bind="$attrs"
>
<slot name="password-toggle" v-if="!disableToggle" :toggle="togglePassword" :type="type">
<a class="VuePassword__Toggle" role="button" @click="togglePassword">
<svg class="VuePassword__Toggle__Icon" viewBox="0 0 32 32" v-if="type === 'password'">
<path d="M16 6c-6.979 0-13.028 4.064-16 10 2.972 5.936 9.021 10 16 10s13.027-4.064 16-10c-2.972-5.936-9.021-10-16-10zM23.889 11.303c1.88 1.199 3.473 2.805 4.67 4.697-1.197 1.891-2.79 3.498-4.67 4.697-2.362 1.507-5.090 2.303-7.889 2.303s-5.527-0.796-7.889-2.303c-1.88-1.199-3.473-2.805-4.67-4.697 1.197-1.891 2.79-3.498 4.67-4.697 0.122-0.078 0.246-0.154 0.371-0.228-0.311 0.854-0.482 1.776-0.482 2.737 0 4.418 3.582 8 8 8s8-3.582 8-8c0-0.962-0.17-1.883-0.482-2.737 0.124 0.074 0.248 0.15 0.371 0.228v0zM16 13c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3z"></path>
</svg>
<svg class="VuePassword__Toggle__Icon" viewBox="0 0 32 32" v-if="type === 'text'">
<path d="M29.561 0.439c-0.586-0.586-1.535-0.586-2.121 0l-6.318 6.318c-1.623-0.492-3.342-0.757-5.122-0.757-6.979 0-13.028 4.064-16 10 1.285 2.566 3.145 4.782 5.407 6.472l-4.968 4.968c-0.586 0.586-0.586 1.535 0 2.121 0.293 0.293 0.677 0.439 1.061 0.439s0.768-0.146 1.061-0.439l27-27c0.586-0.586 0.586-1.536 0-2.121zM13 10c1.32 0 2.44 0.853 2.841 2.037l-3.804 3.804c-1.184-0.401-2.037-1.521-2.037-2.841 0-1.657 1.343-3 3-3zM3.441 16c1.197-1.891 2.79-3.498 4.67-4.697 0.122-0.078 0.246-0.154 0.371-0.228-0.311 0.854-0.482 1.776-0.482 2.737 0 1.715 0.54 3.304 1.459 4.607l-1.904 1.904c-1.639-1.151-3.038-2.621-4.114-4.323z"></path>
<path d="M24 13.813c0-0.849-0.133-1.667-0.378-2.434l-10.056 10.056c0.768 0.245 1.586 0.378 2.435 0.378 4.418 0 8-3.582 8-8z"></path>
<path d="M25.938 9.062l-2.168 2.168c0.040 0.025 0.079 0.049 0.118 0.074 1.88 1.199 3.473 2.805 4.67 4.697-1.197 1.891-2.79 3.498-4.67 4.697-2.362 1.507-5.090 2.303-7.889 2.303-1.208 0-2.403-0.149-3.561-0.439l-2.403 2.403c1.866 0.671 3.873 1.036 5.964 1.036 6.978 0 13.027-4.064 16-10-1.407-2.81-3.504-5.2-6.062-6.938z"></path>
</svg>
</a>
</slot>
</div>
<slot name="strength-meter" v-if="!disableStrength" :strength="this.strength">
<svg viewBox="0 0 123 2" class="VuePassword__Meter" preserveAspectRatio="none">
<path d="M0 1 L30 1" :class="getStrengthClass(0)"></path>
<path d="M31 1 L61 1" :class="getStrengthClass(1)"></path>
<path d="M62 1 L92 1" :class="getStrengthClass(2)"></path>
<path d="M93 1 L123 1" :class="getStrengthClass(3)"></path>
</svg>
</slot>
<slot name="strength-message" v-if="!disableStrength" :strength="this.strength">
<div class="VuePassword__Message" :class="strengthClass">{{ message }}</div>
</slot>
</div>
</template>
<style>
.VuePassword {
position: relative;
}
.VuePassword__Input {
position: relative;
}
.VuePassword input {
padding-right: 2.5em;
width: 100%;
}
.VuePassword__Toggle {
color: gray;
display: inline-block;
height: 100%;
position: absolute;
right: 0;
top: 0;
z-index: 1;
}
.VuePassword__Toggle__Icon {
fill: currentColor;
height: 100%;
width: 1.5em;
margin-right: .5em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.VuePassword__Meter {
color: rgb(175, 175, 175);
display: block;
height: .5rem;
margin-top: .2rem;
padding-left: .5rem;
padding-right: .5rem;
width: 100%;
}
.VuePassword__Meter path {
stroke: currentColor;
stroke-width: 2;
}
.VuePassword--very-weak {
color: rgb(175, 175, 175);
}
.VuePassword--weak {
color: rgb(230, 30, 30);
}
.VuePassword--medium {
color: rgb(255, 160, 65);
}
.VuePassword--good {
color: rgb(100, 200, 75);
}
.VuePassword--great {
color: rgb(75, 150, 50);
}
.VuePassword__Message {
cursor: default;
font-size: 1rem;
padding-left: .5rem;
padding-right: .5rem;
text-align: right;
text-transform: uppercase;
}
</style>
<script>
import passwordStrength from 'zxcvbn'
export default{
inheritAttrs: false,
data () {
return {
type: 'password',
strength: {},
dirty: false
}
},
computed: {
/**
* Get the current password strength message.
*
* @return {string}
*/
message () {
if (this.strength.score >= 0 && this.dirty) {
if (this.strengthMessages[this.strength.score]) {
return this.strengthMessages[this.strength.score]
}
}
},
/**
* Get the current password strength class.
*
* @return {string}
*/
strengthClass () {
if (this.strength.score >= 0) {
if (this.strengthClasses[this.strength.score]) {
return this.strengthClasses[this.strength.score]
}
}
},
/**
* Get the feedback warning or suggestion from zxcvbn.
*
* @return {string}
*/
feedback () {
if (this.strength.feedback) {
if (this.strength.feedback.warning) {
return this.strength.feedback.warning
} else {
return this.strength.feedback.suggestions[0]
}
}
}
},
props: {
/**
* Classes to apply to the password input.
*/
classes: {
type: [String, Array],
default: 'form-control'
},
/**
* Classes to apply for the various strength levels from zxcvbn.
*/
strengthClasses: {
type: Array,
default () {
return [
'VuePassword--very-weak',
'VuePassword--weak',
'VuePassword--medium',
'VuePassword--good',
'VuePassword--great'
]
}
},
/**
* Messages to apply to the various strength levels from zxcvbn.
*/
strengthMessages: {
type: Array,
default () {
return [
'Very Weak',
'Weak',
'Medium',
'Strong',
'Very Strong'
]
}
},
value: {
type: String,
default: ''
},
/**
* Set any additional user inputs to influence the password strength
* calculated by zxcvbn.
*/
userInputs: {
type: Array,
default () {
return []
}
},
/**
* Disable the password toggle.
*/
disableToggle: {
type: Boolean,
default: false
},
/**
* Disable the password strength.
*/
disableStrength: {
type: Boolean,
default: false
}
},
mounted () {
// If a password exists on mount, get the strength from zxcvbn.
if (this.value) {
this.strength = passwordStrength(this.value, this.userInputs)
this.dirty = true
}
},
watch: {
/**
* Watch for changes in the user inputs prop to update the strength score.
*/
userInputs () {
this.strength = this.value === null ? 0 : passwordStrength(this.value, this.userInputs)
}
},
methods: {
/**
* Update the password input.
*
* @param {String} password
*/
updatePassword (password) {
this.$emit('input', password)
this.dirty = true
this.strength = passwordStrength(password, this.userInputs)
this.$emit('strength', this.strength)
},
/**
* Toggle the visibilty of the password.
*/
togglePassword () {
this.type = this.type === 'password' ? 'text' : 'password'
this.$refs.input.setAttribute('type', this.type)
this.$refs.input.focus()
},
/**
* Get the current strength class based on the strength calculated
* by zxcvbn.
*
* @param {Number} strength
* @return {String}
*/
getStrengthClass (strength) {
if (this.strength.score > strength) {
return this.strengthClass
}
return ''
},
emitBlur (event) {
this.$emit('blur', event.target.value)
},
emitFocus (event) {
this.$emit('focus', event.target.value)
}
}
}
</script>
......@@ -7,8 +7,8 @@
<b-tabs v-model='activeTab' position='is-centered' class='block'>
<b-tab-item label='Me' :disabled='!configured'>
<!-- <content-type-filter /> -->
<textComposer ref='textComposer' @composerChange='composerChange' @post='post' v-if='signedIn'/>
<pre style='word-break: break-word;' v-if='signedIn && debug'>{{ composerContent }}</pre>
<textComposer ref='textComposer' @composerChange='composerChange' @post='post' v-show='signedIn'/>
<pre style='word-break: break-word;' v-show='signedIn && debug'>{{ composerContent }}</pre>
<transition-group name='list' tag='div' @enter='clearTextComposer'>
<div v-for='post in posts' :key='post.id'>
<!-- <span>ID: {{post.id}}</span> -->
......@@ -18,8 +18,8 @@
</b-tab-item>
<b-tab-item label="Everyone" :disabled='!configured'><b-message type='is-info' has-icon><strong>Nothing yet.</strong> Federated posts will go here.</b-message></b-tab-item>
<b-tab-item label='Search' :disabled='!configured'><search /></b-tab-item>
<b-tab-item label="Settings" v-if='signedIn || !configured'><settings /></b-tab-item>
<b-tab-item label="…" v-if='signedIn || !configured' :disabled='!configured'>
<b-tab-item label="Settings" v-show='signedIn || !configured'><settings /></b-tab-item>
<b-tab-item label="…" v-show='signedIn || !configured' :disabled='!configured'>
<b-message type='is-info' has-icon><strong>Nothing yet.</strong> Additional features will be listed here.</b-message>
</b-tab-item>
</b-tabs>
......
import Vue from 'vue'
import VuePassword from 'vue-password'
Vue.component('vue-password', VuePassword)
......@@ -26,7 +26,12 @@ export const mutations = {
state.backgroundColour = colourValue.hex
},
configured (state, value) {
console.log(`Store: state.configured = ${value}`)
state.configured = value
},
signedIn (state, value) {
console.log(`Store: state.signedIn = ${value}`)
state.signedIn = value
}
}
......
......@@ -29,7 +29,6 @@ module.exports = {
plugins: [
'~plugins/buefy',
{ src: '~plugins/vue-password', ssr: false },
{ src: '~plugins/vue-quill-editor', ssr: false }
],
......
......@@ -13603,15 +13603,6 @@
"object-assign": "4.1.1"
}
},
"vue-password": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vue-password/-/vue-password-1.1.2.tgz",
"integrity": "sha512-IVouoqvgXnXxFaAu9ej0K0uRr8z7yVvtJhbtiaGpE5+5O18zlvH/wHSIDAEHt2l8RKoFivzYPj/HiQJeMH0xCQ==",
"requires": {
"vue": "2.5.13",
"zxcvbn": "4.4.2"
}
},
"vue-quill-editor": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/vue-quill-editor/-/vue-quill-editor-3.0.5.tgz",
......
......@@ -52,11 +52,11 @@
"serve-favicon": "^2.4.5",
"sweet-modal-vue": "^2.0.0",
"vue-color": "^2.4.5",
"vue-password": "^1.1.2",
"vue-quill-editor": "^3.0.5",
"winston": "^2.4.0",
"ws": "^4.1.0",
"yargs": "^11.0.0"
"yargs": "^11.0.0",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"eslint": "^4.18.2",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment