web/satellite: create passphrase step for objects page

WHAT:
made a common component from AG's generate passphrase component to be reused here.
create passphrase step for objects page

WHY:
used for generating passphrase or setting user's own password for proceeding in upload flow

Change-Id: Iefa106c544a1c5ff04d591d3d400cf06c4c6d853
This commit is contained in:
Vitalii Shpital 2021-02-25 00:53:57 +02:00
parent b5f83f463b
commit 9e20cfb6e0
4 changed files with 426 additions and 273 deletions

View File

@ -2,87 +2,30 @@
// See LICENSE for copying information.
<template>
<div class="create-passphrase" :class="{ 'border-radius': isOnboardingTour }">
<div class="create-passphrase">
<BackIcon class="create-passphrase__back-icon" @click="onBackClick"/>
<h1 class="create-passphrase__title">Encryption Passphrase</h1>
<div class="create-passphrase__warning">
<WarningIcon/>
<p class="create-passphrase__warning__label" v-if="isGenerateState">Save Your Encryption Passphrase</p>
<p class="create-passphrase__warning__label" v-else>Remember This Passphrase</p>
</div>
<div class="create-passphrase__choosing">
<p class="create-passphrase__choosing__label">Choose Passphrase Type</p>
<div class="create-passphrase__choosing__right">
<p
class="create-passphrase__choosing__right__option left-option"
:class="{ active: isGenerateState }"
@click="onChooseGenerate"
>
Generate Phrase
</p>
<p
class="create-passphrase__choosing__right__option"
:class="{ active: isCreateState }"
@click="onChooseCreate"
>
Create Phrase
</p>
</div>
</div>
<div class="create-passphrase__value-area">
<div class="create-passphrase__value-area__mnemonic" v-if="isGenerateState">
<p class="create-passphrase__value-area__mnemonic__label">12-Word Mnemonic Passphrase:</p>
<div class="create-passphrase__value-area__mnemonic__container">
<p class="create-passphrase__value-area__mnemonic__container__value">{{ passphrase }}</p>
<VButton
class="create-passphrase__value-area__mnemonic__container__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopyClick"
/>
</div>
</div>
<div class="create-passphrase__value-area__password" v-else>
<HeaderedInput
class="create-passphrase__value-area__password__input"
label="Create Your Passphrase"
placeholder="Strong passphrases contain 12 characters or more"
@setData="onChangePassphrase"
:error="errorMessage"
/>
</div>
</div>
<VButton
class="create-passphrase__next-button"
label="Next"
width="100%"
height="48px"
:on-press="onNextClick"
:is-disabled="isLoading"
<GeneratePassphrase
:is-loading="isLoading"
:on-button-click="onNextClick"
:set-parent-passphrase="setPassphrase"
/>
</div>
</template>
<script lang="ts">
import * as bip39 from 'bip39';
import { Component, Vue } from 'vue-property-decorator';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import VButton from '@/components/common/VButton.vue';
import GeneratePassphrase from '@/components/common/GeneratePassphrase.vue';
import BackIcon from '@/../static/images/accessGrants/back.svg';
import WarningIcon from '@/../static/images/accessGrants/warning.svg';
import { RouteConfig } from '@/router';
import { MetaUtils } from '@/utils/meta';
@Component({
components: {
WarningIcon,
BackIcon,
VButton,
HeaderedInput,
GeneratePassphrase,
},
})
export default class CreatePassphraseStep extends Vue {
@ -92,10 +35,7 @@ export default class CreatePassphraseStep extends Vue {
private worker: Worker;
private isLoading: boolean = true;
public isGenerateState: boolean = true;
public isCreateState: boolean = false;
public passphrase: string = '';
public errorMessage: string = '';
/**
* Lifecycle hook after initial render.
@ -110,24 +50,12 @@ export default class CreatePassphraseStep extends Vue {
this.key = this.$route.params.key;
this.restrictedKey = this.$route.params.restrictedKey;
this.passphrase = bip39.generateMnemonic();
this.setWorker();
this.isLoading = false;
}
/**
* Changes state to generate passphrase.
*/
public onChooseGenerate(): void {
if (this.passphrase && this.isGenerateState) return;
this.passphrase = bip39.generateMnemonic();
this.isCreateState = false;
this.isGenerateState = true;
}
/**
* Sets local worker with worker instantiated in store.
* Also sets worker's onmessage and onerror logic.
@ -152,33 +80,10 @@ export default class CreatePassphraseStep extends Vue {
}
/**
* Changes state to create passphrase.
* Sets passphrase from child component.
*/
public onChooseCreate(): void {
if (this.passphrase && this.isCreateState) return;
this.errorMessage = '';
this.passphrase = '';
this.isCreateState = true;
this.isGenerateState = false;
}
/**
* Holds on copy button click logic.
* Copies passphrase to clipboard.
*/
public onCopyClick(): void {
this.$copyText(this.passphrase);
this.$notify.success('Passphrase was copied successfully');
}
/**
* Changes passphrase data from input value.
* @param value
*/
public onChangePassphrase(value: string): void {
this.passphrase = value.trim();
this.errorMessage = '';
public setPassphrase(passphrase: string): void {
this.passphrase = passphrase;
}
/**
@ -186,15 +91,11 @@ export default class CreatePassphraseStep extends Vue {
* Generates access grant and redirects to next step.
*/
public onNextClick(): void {
if (!this.passphrase) {
this.errorMessage = 'Passphrase can`t be empty';
return;
}
if (this.isLoading) return;
this.isLoading = true;
const satelliteNodeURL = MetaUtils.getMetaContent('satellite-nodeurl');
const satelliteNodeURL: string = MetaUtils.getMetaContent('satellite-nodeurl');
this.worker.postMessage({
'type': 'GenerateAccess',
@ -259,7 +160,7 @@ export default class CreatePassphraseStep extends Vue {
/**
* Indicates if current route is onboarding tour.
*/
public get isOnboardingTour(): boolean {
private get isOnboardingTour(): boolean {
return this.$route.path.includes(RouteConfig.OnboardingTour.path);
}
}
@ -267,18 +168,7 @@ export default class CreatePassphraseStep extends Vue {
<style scoped lang="scss">
.create-passphrase {
height: calc(100% - 60px);
padding: 30px 65px;
max-width: 475px;
min-width: 475px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
background-color: #fff;
border-radius: 0 6px 6px 0;
&__back-icon {
position: absolute;
@ -286,150 +176,5 @@ export default class CreatePassphraseStep extends Vue {
left: 65px;
cursor: pointer;
}
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 30px 0;
}
&__warning {
display: flex;
align-items: center;
padding: 20px;
width: calc(100% - 40px);
background: #fff9f7;
border: 1px solid #f84b00;
margin-bottom: 35px;
border-radius: 8px;
&__label {
font-style: normal;
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 0 0 0 15px;
}
}
&__choosing {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin: 0;
}
&__right {
display: flex;
align-items: center;
&__option {
font-size: 14px;
line-height: 17px;
color: #768394;
margin: 0;
cursor: pointer;
border-bottom: 3px solid #fff;
}
}
}
&__value-area {
margin: 50px 0 60px 0;
min-height: 100px;
width: 100%;
display: flex;
align-items: flex-start;
&__mnemonic {
&__label {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 19px;
color: #7c8794;
margin: 0 0 10px 0;
}
&__container {
display: flex;
align-items: flex-start;
background: #f5f6fa;
border-radius: 9px;
padding: 10px;
width: calc(100% - 20px);
&__value {
font-family: 'Source Code Pro', sans-serif;
font-size: 14px;
line-height: 25px;
color: #384b65;
word-break: break-word;
margin: 0;
word-spacing: 8px;
}
&__button {
margin-left: 10px;
min-width: 66px;
min-height: 30px;
}
}
}
&__password {
width: 100%;
&__input {
width: calc(100% - 8px);
}
}
}
}
.left-option {
margin-right: 15px;
}
.active {
font-family: 'font_bold', sans-serif;
color: #0068dc;
border-bottom: 3px solid #0068dc;
}
.border-radius {
border-radius: 6px;
}
/deep/ .label-container {
&__main {
margin-bottom: 10px;
&__label {
margin: 0;
font-size: 14px;
line-height: 19px;
color: #7c8794;
font-family: 'font_bold', sans-serif;
}
&__error {
margin: 0 0 0 10px;
font-size: 14px;
line-height: 19px;
}
}
}
</style>

View File

@ -0,0 +1,352 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="generate-container">
<h1 class="generate-container__title">Encryption Passphrase</h1>
<div class="generate-container__warning">
<div class="generate-container__warning__header">
<WarningIcon/>
<p class="generate-container__warning__header__label">Save Your Encryption Passphrase</p>
</div>
<p class="generate-container__warning__message">
Youll need this passphrase to access data in the future. This is the only time it will be displayed.
Be sure to write it down.
</p>
</div>
<div class="generate-container__choosing">
<p class="generate-container__choosing__label">Choose Passphrase Type</p>
<div class="generate-container__choosing__right">
<p
class="generate-container__choosing__right__option left-option"
:class="{ active: isGenerateState }"
@click="onChooseGenerate"
>
Generate Phrase
</p>
<p
class="generate-container__choosing__right__option"
:class="{ active: isCreateState }"
@click="onChooseCreate"
>
Create Phrase
</p>
</div>
</div>
<div class="generate-container__value-area">
<div class="generate-container__value-area__mnemonic" v-if="isGenerateState">
<p class="generate-container__value-area__mnemonic__value">{{ passphrase }}</p>
<VButton
class="generate-container__value-area__mnemonic__button"
label="Copy"
width="66px"
height="30px"
:on-press="onCopyClick"
/>
</div>
<div class="generate-container__value-area__password" v-else>
<HeaderedInput
class="generate-container__value-area__password__input"
placeholder="Strong passphrases contain 12 characters or more"
@setData="onChangePassphrase"
:error="errorMessage"
label="Create Your Passphrase"
/>
</div>
</div>
<label class="generate-container__check-area" :class="{ error: isError }" for="pass-checkbox">
<input
class="generate-container__check-area__checkbox"
id="pass-checkbox"
type="checkbox"
v-model="isChecked"
@change="isError = false"
>
Yes, I wrote this down or saved it somewhere.
</label>
<VButton
class="generate-container__next-button"
label="Next"
width="100%"
height="48px"
:on-press="onProceed"
:is-disabled="isLoading"
/>
</div>
</template>
<script lang="ts">
import * as bip39 from 'bip39';
import { Component, Prop, Vue } from 'vue-property-decorator';
import HeaderedInput from '@/components/common/HeaderedInput.vue';
import VButton from '@/components/common/VButton.vue';
import BackIcon from '@/../static/images/accessGrants/back.svg';
import WarningIcon from '@/../static/images/accessGrants/warning.svg';
@Component({
components: {
WarningIcon,
BackIcon,
VButton,
HeaderedInput,
},
})
export default class GeneratePassphrase extends Vue {
@Prop({ default: () => null })
public readonly onButtonClick: () => void;
@Prop({ default: () => null })
public readonly setParentPassphrase: (passphrase: string) => void;
@Prop({ default: false })
public readonly isLoading: boolean;
public isGenerateState: boolean = true;
public isCreateState: boolean = false;
public isChecked: boolean = false;
public isError: boolean = false;
public passphrase: string = '';
public errorMessage: string = '';
/**
* Lifecycle hook after initial render.
* Generates mnemonic string.
*/
public mounted(): void {
this.passphrase = bip39.generateMnemonic();
this.setParentPassphrase(this.passphrase);
}
public onProceed(): void {
if (!this.passphrase) {
this.errorMessage = 'Passphrase can`t be empty';
return;
}
if (!this.isChecked) {
this.isError = true;
return;
}
this.onButtonClick();
}
/**
* Changes state to generate passphrase.
*/
public onChooseGenerate(): void {
if (this.passphrase && this.isGenerateState) return;
this.passphrase = bip39.generateMnemonic();
this.setParentPassphrase(this.passphrase);
this.isCreateState = false;
this.isGenerateState = true;
}
/**
* Changes state to create passphrase.
*/
public onChooseCreate(): void {
if (this.passphrase && this.isCreateState) return;
this.errorMessage = '';
this.passphrase = '';
this.setParentPassphrase(this.passphrase);
this.isCreateState = true;
this.isGenerateState = false;
}
/**
* Holds on copy button click logic.
* Copies passphrase to clipboard.
*/
public onCopyClick(): void {
this.$copyText(this.passphrase);
this.$notify.success('Passphrase was copied successfully');
}
/**
* Changes passphrase data from input value.
* @param value
*/
public onChangePassphrase(value: string): void {
this.passphrase = value.trim();
this.setParentPassphrase(this.passphrase);
this.errorMessage = '';
}
}
</script>
<style scoped lang="scss">
.generate-container {
padding: 25px 50px;
max-width: 515px;
min-width: 515px;
font-family: 'font_regular', sans-serif;
font-style: normal;
display: flex;
flex-direction: column;
align-items: center;
background-color: #fff;
border-radius: 6px;
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 22px;
line-height: 27px;
color: #000;
margin: 0 0 30px 0;
}
&__warning {
display: flex;
flex-direction: column;
padding: 20px;
width: calc(100% - 40px);
background: #fff9f7;
border: 1px solid #f84b00;
margin-bottom: 35px;
border-radius: 8px;
&__header {
display: flex;
align-items: center;
&__label {
font-style: normal;
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 0 0 0 15px;
}
}
&__message {
font-size: 16px;
line-height: 19px;
color: #1b2533;
margin: 10px 0 0 0;
}
}
&__choosing {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin: 0;
}
&__right {
display: flex;
align-items: center;
&__option {
font-size: 14px;
line-height: 17px;
color: #768394;
margin: 0;
cursor: pointer;
border-bottom: 3px solid #fff;
}
}
}
&__value-area {
margin: 32px 0;
width: 100%;
display: flex;
align-items: flex-start;
&__mnemonic {
display: flex;
background: #f5f6fa;
border-radius: 9px;
padding: 10px;
width: calc(100% - 20px);
&__value {
font-family: 'Source Code Pro', sans-serif;
font-size: 14px;
line-height: 25px;
color: #384b65;
word-break: break-word;
margin: 0;
word-spacing: 8px;
}
&__button {
margin-left: 10px;
min-width: 66px;
min-height: 30px;
}
}
&__password {
width: 100%;
&__input {
width: calc(100% - 8px);
}
}
}
&__check-area {
margin-bottom: 32px;
font-size: 14px;
line-height: 19px;
color: #1b2533;
&__checkbox {
margin: 0 10px 0 0;
}
}
}
.left-option {
margin-right: 15px;
}
.active {
font-family: 'font_bold', sans-serif;
color: #0068dc;
border-bottom: 3px solid #0068dc;
}
.error {
color: red;
}
/deep/ .label-container {
&__main {
margin-bottom: 10px;
&__label {
margin: 0;
font-size: 14px;
line-height: 19px;
color: #7c8794;
font-family: 'font_bold', sans-serif;
}
&__error {
margin: 0 0 0 10px;
font-size: 14px;
line-height: 19px;
}
}
}
</style>

View File

@ -1,9 +1,51 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="create-pass">
<GeneratePassphrase
:is-loading="isLoading"
:on-button-click="onNextClick"
:set-parent-passphrase="setPassphrase"
/>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component
export default class CreatePassphrase extends Vue {}
import GeneratePassphrase from '@/components/common/GeneratePassphrase.vue';
@Component({
components: {
GeneratePassphrase,
},
})
export default class CreatePassphrase extends Vue {
private isLoading: boolean = false;
public passphrase: string = '';
/**
* Sets passphrase from child component.
*/
public setPassphrase(passphrase: string): void {
this.passphrase = passphrase;
}
/**
* Holds on next button click logic.
*/
public onNextClick(): void {
if (this.isLoading) return;
this.isLoading = true;
}
}
</script>
<style scoped lang="scss">
.create-pass {
margin-top: 150px;
}
</style>

View File

@ -4,7 +4,7 @@
<template>
<div class="objects-area">
<div class="objects-area__header">
<h1 class="objects-area__header__title">File</h1>
<h1 class="objects-area__header__title">Objects</h1>
</div>
<router-view/>
</div>
@ -13,18 +13,32 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { RouteConfig } from '@/router';
@Component
export default class ObjectsArea extends Vue {}
export default class ObjectsArea extends Vue {
/**
* Lifecycle hook after initial render.
* Chooses correct route.
*/
public mounted(): void {
this.$router.push(RouteConfig.Objects.with(RouteConfig.CreatePassphrase).path);
}
}
</script>
<style scoped lang="scss">
.objects-area {
padding: 20px 45px;
display: flex;
flex-direction: column;
align-items: center;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
&__title {
font-family: 'font_medium', sans-serif;
@ -32,7 +46,7 @@ export default class ObjectsArea extends Vue {}
font-weight: bold;
font-size: 18px;
line-height: 26px;
color: #232B34;
color: #232b34;
margin: 0;
}
}