Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions frontend/assets/style/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ $payment-table-desktop-bp: 1290px;
width: $dimensions;
}

@mixin tooltip-style-common {
position: absolute;
top: 0;
left: 0;
min-width: 3rem;
max-width: 12rem;
border-radius: 3px;
padding: 0.5rem;
z-index: 50; // $zindex-tooltip: 50;
pointer-events: none;
background-color: var(--text_0);
opacity: 0.95;
color: var(--background_0);
}

@mixin overflow-touch {
-webkit-overflow-scrolling: touch;
}
Expand Down
13 changes: 1 addition & 12 deletions frontend/views/components/Tooltip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -375,18 +375,7 @@ export default ({

.c-tooltip,
.c-anchored-tooltip {
position: absolute;
top: 0;
left: 0;
min-width: 3rem;
max-width: 12rem;
border-radius: $radius;
padding: 0.5rem;
z-index: $zindex-tooltip;
pointer-events: none;
background-color: $text_0;
opacity: 0.95;
color: $background_0;
@include tooltip-style-common;

&.has-text-center {
text-align: center;
Expand Down
129 changes: 120 additions & 9 deletions frontend/views/containers/access/PasswordForm.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
<template lang='pug'>
label.field
component.field(:is='mode === "manual" ? "label" : "div"')
.label(v-if='label') {{ label }}

.inputgroup.c-mode-auto(
v-if='mode === "auto"'
v-error:[name]=''
)
.input.width-with-single-addon.has-ellipsis.c-auto-password(
:data-test='name'
) {{ ephemeral.randomPassword }}

.addons
button.is-success.c-copy-btn(
type='button'
@click.stop='copyPassword'
)
i18n Copy

i18n.c-feedback(
v-if='ephemeral.showCopyFeedback'
) Copied to clipboard!

.inputgroup(
v-error:[name]='{ attrs: { "data-test": "badPassword" }}'
v-else
v-error:[name]=''
)
input.input.with-single-addon(
:type='isLock ? "password" : "text"'
Expand All @@ -17,7 +38,6 @@ label.field
.addons
button.is-icon(
type='button'
v-if='hasIconRight'
:aria-label='L("Toggle password visibility")'
:aria-pressed='!isLock'
@click.prevent='isLock = !isLock'
Expand All @@ -26,13 +46,31 @@ label.field
</template>

<script>
import { base58btc } from 'multiformats/bases/base58'
import Tooltip from '@components/Tooltip.vue'
import validationsDebouncedMixins from '@view-utils/validationsDebouncedMixins.js'
import { L } from '@common/common.js'

function generateBase58Password (length = 32) {
const bytes = crypto.getRandomValues(new Uint8Array((length)))
const encoded = base58btc.encode(bytes).substring(1) // remove the prefix 'z'

// Truncate to desired length
return encoded.slice(0, length)
}

export default ({
name: 'PasswordForm',
components: {
Tooltip
},
data () {
return {
isLock: true
isLock: true,
ephemeral: {
randomPassword: '',
showCopyFeedback: false
}
}
},
mixins: [validationsDebouncedMixins],
Expand All @@ -42,6 +80,11 @@ export default ({
required: false,
default: 'password'
},
mode: {
type: String,
requried: false,
default: 'manual' // 'manual' | 'auto'
},
label: {
type: String,
required: false
Expand All @@ -50,10 +93,6 @@ export default ({
type: Object,
required: true
},
hasIconRight: {
type: Boolean,
default: true
},
showPlaceholder: {
type: Boolean,
default: false
Expand All @@ -67,15 +106,87 @@ export default ({
required: false
}
},
methods: {
generateRandomPassword (pwLen = 32) {
let genPassword = ''

if (window.Cypress) {
// For easier debugging, use the common default password in Cypress test.
genPassword = '123456789'
} else {
genPassword = generateBase58Password(pwLen)
}

this.ephemeral.randomPassword = genPassword
this.$v.form[this.name].$model = genPassword
},
copyPassword () {
const pw = this.ephemeral.randomPassword
const copyToClipBoard = () => {
navigator.clipboard.writeText(pw)
this.ephemeral.showCopyFeedback = true

setTimeout(() => {
this.ephemeral.showCopyFeedback = false
}, 1500)
}

if (navigator.share) {
navigator.share({
title: L('Your password'),
text: pw
}).catch((error) => {
console.error('navigator.share failed with:', error)
copyToClipBoard()
})
} else {
copyToClipBoard()
}
}
},
created () {
this.isLock = !this.showPassword
if (this.mode === 'auto') {
this.generateRandomPassword()
} else {
this.isLock = !this.showPassword
}
}
}: Object)
</script>

<style lang="scss" scoped>
@import "@assets/style/_variables.scss";

.c-mode-auto {
.c-auto-password {
display: block;
line-height: 2.75rem;
padding-right: 5.5rem;
}

.addons {
align-items: center;
right: 0.5rem;
}
}

.icon {
cursor: pointer;
pointer-events: initial !important;
}

button.c-copy-btn {
min-height: unset;
height: 1.75rem;
border-radius: 3px;
padding-left: 1rem;
padding-right: 1rem;
}

.c-feedback {
@include tooltip-style-common;
top: calc(100% + 0.5rem);
left: 50%;
transform: translateX(-50%);
}
</style>
3 changes: 0 additions & 3 deletions frontend/views/containers/access/PasswordModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ modal-template(class='is-centered is-left-aligned' back-on-mobile=true ref='moda
:value='form'
:$v='$v'
@enter='changePassword'
:hasIconRight='true'
:showPlaceholder='false'
:showPassword='false'
size='is-large'
Expand All @@ -26,7 +25,6 @@ modal-template(class='is-centered is-left-aligned' back-on-mobile=true ref='moda
:value='form'
:$v='$v'
@enter='changePassword'
:hasIconRight='true'
:showPlaceholder='false'
:showPassword='false'
size='is-large'
Expand All @@ -38,7 +36,6 @@ modal-template(class='is-centered is-left-aligned' back-on-mobile=true ref='moda
:value='form'
:$v='$v'
@enter='changePassword'
:hasIconRight='true'
:showPlaceholder='false'
:showPassword='false'
size='is-large'
Expand Down
66 changes: 45 additions & 21 deletions frontend/views/containers/access/SignupForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,36 @@ form(data-test='signup' @submit.prevent='')
v-error:username='{ attrs: { "data-test": "badUsername" } }'
)

.c-password-fields-container
password-form(:label='L("Password")' name='password' :$v='$v')
.c-auto-password-field-container
password-form(
mode='auto'
:label='L("This is your password. Save it now.")'
name='password'
:$v='$v'
)

password-form(:label='L("Confirm Password")' name='passwordConfirm' :$v='$v')
.c-confirm-check-container
label.checkbox
input.input(
type='checkbox'
name='savedPassword'
v-model='form.savedPassword'
data-test='savedPassword'
@click.stop=''
)
i18n I have saved the password

label.checkbox
input.input(
type='checkbox'
name='terms'
v-model='form.terms'
data-test='signTerms'
@click.stop=''
)
i18n(
:args='{ a_: `<a class="link" target="_blank" href="${linkToTerms}">`, _a: "</a>"}'
) I agree to the {a_}terms and conditions{_a}
label.checkbox
input.input(
type='checkbox'
name='terms'
v-model='form.terms'
data-test='signTerms'
@click.stop=''
)
i18n(
:args='{ a_: `<a class="link" target="_blank" href="${linkToTerms}">`, _a: "</a>"}'
) I agree to the {a_}terms and conditions{_a}

banner-scoped(ref='formMsg' allow-a)

Expand All @@ -44,7 +58,7 @@ form(data-test='signup' @submit.prevent='')
<script>
import sbp from '@sbp/sbp'
import { L } from '@common/common.js'
import { maxLength, minLength, required, sameAs } from 'vuelidate/lib/validators'
import { maxLength, minLength, required } from 'vuelidate/lib/validators'
import { validationMixin } from 'vuelidate'
import PasswordForm from '@containers/access/PasswordForm.vue'
import BannerScoped from '@components/banners/BannerScoped.vue'
Expand Down Expand Up @@ -107,7 +121,7 @@ export default ({
form: {
username: '',
password: '',
passwordConfirm: '',
savedPassword: false,
terms: false,
pictureBase64: ''
},
Expand Down Expand Up @@ -175,13 +189,11 @@ export default ({
[L('A password is required.')]: required,
[L('Your password must be at least {minChars} characters long.', { minChars: passwordMinChars })]: minLength(passwordMinChars)
},
passwordConfirm: {
[L('Passwords do not match.')]: sameAs('password')
savedPassword: {
[L('Please save the password.')]: (value) => Boolean(value)
},
terms: {
[L('You need to agree to the terms and conditions.')]: (value) => {
return Boolean(value)
}
[L('You need to agree to the terms and conditions.')]: (value) => Boolean(value)
}
}
}
Expand All @@ -208,4 +220,16 @@ export default ({
margin-bottom: 1.5rem;
}
}

.c-auto-password-field-container {
margin: 1.5rem 0;
}

.c-confirm-check-container {
position: relative;
display: flex;
flex-direction: column;
row-gap: 0.5rem;
margin-bottom: 2rem;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
:label='L("Password")'
:value='form'
:$v='$v'
:hasIconRight='true'
:showPlaceholder='false'
:showPassword='false'
size='is-large'
Expand Down
11 changes: 9 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading