web/satellite: added passphrase generated screen for new access grant flow

Added passphrase generated screen.
Passphrase is a 12-word mnemonic in this case and it is generated with bip39 lib.
It can be copied to clipboard or downloaded as a .txt file.

Change-Id: I031f6c0e92f4f783c07a2d8d35c0433c1d9a81ff
This commit is contained in:
Vitalii 2023-02-15 19:28:21 +02:00
parent 16b7901fde
commit a9ca643e3f
16 changed files with 367 additions and 59 deletions

View File

@ -44,7 +44,7 @@
:is-new-passphrase="false"
:on-back="() => setStep(CreateAccessStep.AccessEncryption)"
:on-continue="() => setStep(CreateAccessStep.AccessCreated)"
:passphrase="passphrase"
:passphrase="enteredPassphrase"
:set-passphrase="setPassphrase"
info="Enter the encryption passphrase used for this project to create this access grant."
/>
@ -53,11 +53,18 @@
:is-new-passphrase="true"
:on-back="() => setStep(CreateAccessStep.AccessEncryption)"
:on-continue="() => setStep(CreateAccessStep.AccessCreated)"
:passphrase="passphrase"
:passphrase="enteredPassphrase"
:set-passphrase="setPassphrase"
info="This passphrase will be used to encrypt all the files you upload using this access grant.
You will need it to access these files in the future."
/>
<PassphraseGeneratedStep
v-if="step === CreateAccessStep.PassphraseGenerated"
:on-back="() => setStep(CreateAccessStep.AccessEncryption)"
:on-continue="() => setStep(CreateAccessStep.AccessCreated)"
:passphrase="generatedPassphrase"
:name="accessName"
/>
</div>
</template>
</VModal>
@ -65,6 +72,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { generateMnemonic } from 'bip39';
import { useNotify, useRoute, useRouter, useStore } from '@/utils/hooks';
import { RouteConfig } from '@/router';
@ -83,6 +91,7 @@ import CreateNewAccessStep from '@/components/accessGrants/newCreateFlow/steps/C
import ChoosePermissionStep from '@/components/accessGrants/newCreateFlow/steps/ChoosePermissionStep.vue';
import AccessEncryptionStep from '@/components/accessGrants/newCreateFlow/steps/AccessEncryptionStep.vue';
import EnterPassphraseStep from '@/components/accessGrants/newCreateFlow/steps/EnterPassphraseStep.vue';
import PassphraseGeneratedStep from '@/components/accessGrants/newCreateFlow/steps/PassphraseGeneratedStep.vue';
const router = useRouter();
const route = useRoute();
@ -117,7 +126,8 @@ const selectedBuckets = ref<string[]>([]);
const passphraseOption = ref<PassphraseOption>(
isPromptForPassphrase.value ? PassphraseOption.SetMyProjectPassphrase : PassphraseOption.UseExistingPassphrase,
);
const passphrase = ref<string>(storedPassphrase.value);
const enteredPassphrase = ref<string>('');
const generatedPassphrase = ref<string>('');
const accessName = ref<string>('');
const notAfter = ref<Date | undefined>(undefined);
const notAfterLabel = ref<string>('No end date');
@ -180,10 +190,10 @@ function setPassphraseOption(option: PassphraseOption): void {
}
/**
* Sets passphrase.
* Sets entered passphrase.
*/
function setPassphrase(value: string): void {
passphrase.value = value;
enteredPassphrase.value = value;
}
/**
@ -316,6 +326,8 @@ onMounted(async () => {
selectedAccessTypes.value.push(route.params?.accessType as AccessType);
}
generatedPassphrase.value = generateMnemonic();
try {
await store.dispatch(BUCKET_ACTIONS.FETCH_ALL_BUCKET_NAMES);
} catch (error) {
@ -335,7 +347,7 @@ onMounted(async () => {
display: flex;
align-items: center;
padding-bottom: 16px;
border-bottom: 1px solid #ebeef1;
border-bottom: 1px solid var(--c-grey-2);
&__title {
margin-left: 16px;
@ -343,7 +355,7 @@ onMounted(async () => {
font-size: 24px;
line-height: 31px;
letter-spacing: -0.02em;
color: #000;
color: var(--c-black);
}
}
}

View File

@ -3,16 +3,39 @@
<template>
<div class="buttons">
<slot name="leftButton" />
<slot name="rightButton" />
<p v-if="label" class="buttons__label">{{ label }}</p>
<div class="buttons__row">
<slot name="leftButton" />
<slot name="rightButton" />
</div>
</div>
</template>
<script setup lang="ts">
const props = withDefaults(defineProps<{
label?: string;
}>(), {
label: '',
});
</script>
<style scoped lang="scss">
.buttons {
display: flex;
align-items: center;
column-gap: 16px;
margin-top: 24px;
margin-top: 16px;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-grey-6);
text-align: left;
margin-bottom: 16px;
}
&__row {
align-items: center;
column-gap: 16px;
display: flex;
}
}
</style>

View File

@ -29,7 +29,7 @@ const slots = useSlots();
<style scoped lang="scss">
.section {
padding: 16px 0;
border-bottom: 1px solid #ebeef1;
border-bottom: 1px solid var(--c-grey-2);
&__wrapper {
display: flex;
@ -47,7 +47,7 @@ const slots = useSlots();
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: #000;
color: var(--c-black);
text-align: left;
margin-bottom: 8px;
}

View File

@ -119,9 +119,9 @@ function onOneYearClick(): void {
<style scoped lang="scss">
.date-picker {
background: #fff;
background: var(--c-white);
width: 410px;
border: 1px solid #384b65;
border: 1px solid var(--c-grey-7);
border-radius: 6px;
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
position: absolute;
@ -141,13 +141,13 @@ function onOneYearClick(): void {
font-size: 14px;
font-weight: 400;
padding: 10px 12px;
color: #1b2533;
color: var(--c-grey-8);
cursor: pointer;
white-space: nowrap;
&:hover {
font-weight: bold;
background: #f5f6fa;
background: var(--c-grey-0);
}
}
}

View File

@ -55,7 +55,7 @@ function togglePicker(): void {
<style scoped lang="scss">
.date-select {
background-color: #fff;
background-color: var(--c-white);
cursor: pointer;
border-radius: 6px;
border: 1px solid rgb(56 75 101 / 40%);
@ -74,7 +74,7 @@ function togglePicker(): void {
&__label {
font-size: 16px;
line-height: 21px;
color: #384b65;
color: var(--c-grey-7);
margin: 0;
}
}

View File

@ -58,7 +58,7 @@ const props = defineProps<{
}
&__message {
color: #fff;
color: var(--c-white);
}
}
}

View File

@ -75,7 +75,7 @@ const props = withDefaults(defineProps<{
left: 0;
height: 16px;
width: 16px;
border: 1px solid #c8d3de;
border: 1px solid var(--c-grey-4);
border-radius: 4px;
box-sizing: border-box;
@ -87,7 +87,7 @@ const props = withDefaults(defineProps<{
top: 1px;
width: 3px;
height: 7px;
border: solid white;
border: solid var(--c-white);
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
@ -98,15 +98,15 @@ const props = withDefaults(defineProps<{
}
input:checked ~ span {
border: 2px solid #376fff;
background-color: #376fff;
border: 2px solid var(--c-light-blue-5);
background-color: var(--c-light-blue-5);
}
&:hover {
input:checked ~ span {
border: 2px solid #376fff;
background-color: #376fff;
border: 2px solid var(--c-light-blue-5);
background-color: var(--c-light-blue-5);
}
}
}

View File

@ -0,0 +1,92 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="blured-container">
<p v-if="isMnemonic" class="blured-container__mnemonic">{{ value }}</p>
<p v-else class="blured-container__text">{{ value }}</p>
<CopyIcon v-if="!isMnemonic" class="blured-container__copy" />
<div v-if="!isValueShown" class="blured-container__blur">
<VButton
:label="buttonLabel"
icon="lock"
width="159px"
height="40px"
font-size="12px"
:is-white="true"
:on-press="showValue"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import VButton from '@/components/common/VButton.vue';
import CopyIcon from '@/../static/images/accessGrants/newCreateFlow/copy.svg';
const props = defineProps<{
isMnemonic: boolean;
value: string;
buttonLabel: string;
}>();
const isValueShown = ref<boolean>(false);
/**
* Makes blurred value to be shown.
*/
function showValue(): void {
isValueShown.value = true;
}
</script>
<style scoped lang="scss">
.blured-container {
display: flex;
align-items: center;
font-family: 'font_regular', sans-serif;
padding: 10px 16px;
background: var(--c-grey-2);
border: 1px solid var(--c-grey-3);
border-radius: 10px;
position: relative;
&__mnemonic {
font-size: 14px;
line-height: 26px;
color: var(--c-black);
text-align: justify-all;
}
&__text {
font-size: 14px;
line-height: 20px;
color: var(--c-grey-7);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 16px;
}
&__copy {
min-width: 16px;
cursor: pointer;
}
&__blur {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
backdrop-filter: blur(10px);
}
}
</style>

View File

@ -159,7 +159,7 @@ function isSelectedOption(option: PassphraseOption): boolean {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: #000;
color: var(--c-black);
text-align: left;
cursor: pointer;
}
@ -177,8 +177,8 @@ function isSelectedOption(option: PassphraseOption): boolean {
}
&__warning-container {
background: #fec;
border: 1px solid #ffd78a;
background: var(--c-yellow-1);
border: 1px solid var(--c-yellow-2);
box-shadow: 0 7px 20px rgb(0 0 0 / 15%);
border-radius: 10px;
padding: 16px;
@ -194,7 +194,7 @@ function isSelectedOption(option: PassphraseOption): boolean {
&__message {
font-size: 14px;
line-height: 20px;
color: #000;
color: var(--c-black);
text-align: left;
}
@ -202,7 +202,7 @@ function isSelectedOption(option: PassphraseOption): boolean {
font-weight: bold;
font-size: 14px;
line-height: 22px;
color: #000;
color: var(--c-black);
font-style: italic;
text-align: left;
}

View File

@ -231,8 +231,8 @@ function toggleBucketsVisibility(): void {
&__search-container {
display: flex;
align-items: center;
background: #fff;
border: 1px solid #d8dee3;
background: var(--c-white);
border: 1px solid var(--c-grey-3);
border-radius: 8px;
padding: 12px;
margin-top: 16px;
@ -245,7 +245,7 @@ function toggleBucketsVisibility(): void {
input {
font-size: 14px;
line-height: 20px;
color: #000;
color: var(--c-black);
border: none;
outline: none;
}
@ -265,7 +265,7 @@ function toggleBucketsVisibility(): void {
align-items: center;
box-sizing: border-box;
padding: 6px 16px;
border: 1px solid #d8dee3;
border: 1px solid var(--c-grey-3);
box-shadow: 0 0 20px rgb(0 0 0 / 4%);
border-radius: 8px;
@ -274,7 +274,7 @@ function toggleBucketsVisibility(): void {
font-family: 'font_bold', sans-serif;
font-size: 12px;
line-height: 20px;
color: #000;
color: var(--c-black);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@ -291,18 +291,18 @@ function toggleBucketsVisibility(): void {
padding: 4px 0;
max-height: 120px;
overflow-y: auto;
border: 1px solid #d8dee3;
border: 1px solid var(--c-grey-3);
box-shadow: 0 4px 6px -2px rgb(0 0 0 / 5%);
border-radius: 6px;
max-width: 100%;
box-sizing: border-box;
&__item {
background-color: #fff;
background-color: var(--c-white);
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #000;
color: var(--c-black);
padding: 10px 16px;
overflow: hidden;
white-space: nowrap;
@ -311,7 +311,7 @@ function toggleBucketsVisibility(): void {
cursor: pointer;
&:hover {
background-color: #ecedf2;
background-color: var(--c-grey-2);
}
}
@ -320,7 +320,7 @@ function toggleBucketsVisibility(): void {
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #000;
color: var(--c-black);
text-align: left;
}
}
@ -333,7 +333,7 @@ function toggleBucketsVisibility(): void {
line-height: 22px;
text-decoration: underline;
text-underline-position: under;
color: #56606d;
color: var(--c-grey-6);
text-align: left;
cursor: pointer;
}

View File

@ -137,15 +137,15 @@ const isButtonDisabled = computed((): boolean => {
row-gap: 16px;
&__info {
color: #fff;
color: var(--c-white);
&__link {
color: #fff;
color: var(--c-white);
text-decoration: underline !important;
text-underline-position: under;
&:visited {
color: #fff;
color: var(--c-white);
}
}
}
@ -161,8 +161,8 @@ const isButtonDisabled = computed((): boolean => {
justify-content: center;
width: 100%;
height: 48px;
background: #fff;
border: 1px solid #d8dee3;
background: var(--c-white);
border: 1px solid var(--c-grey-3);
box-shadow: 0 0 3px rgb(0 0 0 / 8%);
border-radius: 8px;
@ -171,20 +171,20 @@ const isButtonDisabled = computed((): boolean => {
font-size: 14px;
line-height: 24px;
letter-spacing: -0.02em;
color: #56606d;
color: var(--c-grey-6);
margin-left: 8px;
}
&:hover {
border-color: #2683ff;
background-color: #2683ff;
border-color: var(--c-light-blue-6);
background-color: var(--c-light-blue-6);
p {
color: #fff;
color: var(--c-white);
}
:deep(svg path) {
fill: #fff;
fill: var(--c-white);
}
}
}

View File

@ -9,6 +9,7 @@
label="Encryption Passphrase"
placeholder="Enter Encryption Passphrase"
:is-password="true"
:init-value="passphrase"
@setData="setPassphrase"
/>
</div>
@ -93,21 +94,21 @@ function togglePassphraseSaved(): void {
&__info {
font-size: 14px;
line-height: 20px;
color: #091c45;
color: var(--c-blue-6);
padding: 16px 0;
margin-bottom: 16px;
border-bottom: 1px solid #ebeef1;
border-bottom: 1px solid var(--c-grey-2);
text-align: left;
}
&__input-container {
padding-bottom: 16px;
border-bottom: 1px solid #ebeef1;
border-bottom: 1px solid var(--c-grey-2);
}
&__toggle-container {
padding: 16px 0;
border-bottom: 1px solid #ebeef1;
border-bottom: 1px solid var(--c-grey-2);
}
}
</style>

View File

@ -0,0 +1,164 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="generated">
<p class="generated__info">
This passphrase will be used to encrypt all the files you upload using this access grant. You will need it
to access these files in the future.
</p>
<ButtonsContainer label="Save your encryption passphrase">
<template #leftButton>
<VButton
v-clipboard:copy="passphrase"
:label="isPassphraseCopied ? 'Copied' : 'Copy'"
width="100%"
height="40px"
font-size="14px"
:on-press="onCopy"
:icon="isPassphraseCopied ? 'none' : 'copy'"
:is-white="!isPassphraseCopied"
:is-white-green="isPassphraseCopied"
/>
</template>
<template #rightButton>
<VButton
:label="isPassphraseDownloaded ? 'Downloaded' : 'Download'"
width="100%"
height="40px"
font-size="14px"
:on-press="onDownload"
:icon="isPassphraseDownloaded ? 'none' : 'download'"
:is-white="!isPassphraseDownloaded"
:is-green-white="isPassphraseDownloaded"
/>
</template>
</ButtonsContainer>
<div class="generated__blurred">
<ValueWithBlur
button-label="Show Passphrase"
:is-mnemonic="true"
:value="passphrase"
/>
</div>
<div class="generated__toggle-container">
<Toggle
:checked="isPassphraseSaved"
:on-check="togglePassphraseSaved"
label="Yes, I saved my encryption passphrase."
/>
</div>
<ButtonsContainer>
<template #leftButton>
<VButton
label="Back"
width="100%"
height="48px"
font-size="14px"
:on-press="onBack"
:is-white="true"
/>
</template>
<template #rightButton>
<VButton
label="Create Access ->"
width="100%"
height="48px"
font-size="14px"
:on-press="onContinue"
:is-disabled="isButtonDisabled"
/>
</template>
</ButtonsContainer>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useNotify, useStore } from '@/utils/hooks';
import { Download } from '@/utils/download';
import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { AnalyticsHttpApi } from '@/api/analytics';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import ValueWithBlur from '@/components/accessGrants/newCreateFlow/components/ValueWithBlur.vue';
import Toggle from '@/components/accessGrants/newCreateFlow/components/Toggle.vue';
import VButton from '@/components/common/VButton.vue';
const props = defineProps<{
name: string;
passphrase: string;
onBack: () => void;
onContinue: () => void;
}>();
const store = useStore();
const notify = useNotify();
const isPassphraseSaved = ref<boolean>(false);
const isPassphraseCopied = ref<boolean>(false);
const isPassphraseDownloaded = ref<boolean>(false);
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
/**
* Indicates if continue button is disabled.
*/
const isButtonDisabled = computed((): boolean => {
return !(props.passphrase && isPassphraseSaved.value);
});
/**
* Toggles 'passphrase is saved' checkbox.
*/
function togglePassphraseSaved(): void {
isPassphraseSaved.value = !isPassphraseSaved.value;
}
/**
* Saves passphrase to clipboard.
*/
function onCopy(): void {
isPassphraseCopied.value = true;
analytics.eventTriggered(AnalyticsEvent.COPY_TO_CLIPBOARD_CLICKED);
notify.success(`Passphrase was copied successfully`);
}
/**
* Downloads passphrase into .txt file.
*/
function onDownload(): void {
isPassphraseDownloaded.value = true;
Download.file(props.passphrase, `passphrase-${props.name}-${new Date().toISOString()}.txt`);
analytics.eventTriggered(AnalyticsEvent.DOWNLOAD_TXT_CLICKED);
}
</script>
<style lang="scss" scoped>
.generated {
font-family: 'font_regular', sans-serif;
&__info {
font-size: 14px;
line-height: 20px;
color: var(--c-blue-6);
padding: 16px 0;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
text-align: left;
}
&__toggle-container {
padding: 16px 0;
border-bottom: 1px solid var(--c-grey-2);
}
&__blurred {
margin-top: 16px;
padding: 16px 0;
border-top: 1px solid var(--c-grey-2);
border-bottom: 1px solid var(--c-grey-2);
}
}
</style>

View File

@ -15,6 +15,7 @@
<div v-if="isGreenWhite" class="whiteCheck">&#x2713;</div>
<span class="label" :class="{uppercase: isUppercase}">
<CopyIcon v-if="icon.toLowerCase() === 'copy'" />
<DownloadIcon v-if="icon.toLowerCase() === 'download'" />
<LockIcon v-if="icon.toLowerCase() === 'lock'" />
<CreditCardIcon v-if="icon.toLowerCase() === 'credit-card'" />
<DocumentIcon v-if="icon.toLowerCase() === 'document'" />
@ -33,6 +34,7 @@ import TrashIcon from '@/../static/images/accessGrants/trashIcon.svg';
import LockIcon from '@/../static/images/common/lockIcon.svg';
import CreditCardIcon from '@/../static/images/common/creditCardIcon-white.svg';
import DocumentIcon from '@/../static/images/common/documentIcon.svg';
import DownloadIcon from '@/../static/images/common/download.svg';
import FolderIcon from '@/../static/images/objects/newFolder.svg';
const props = withDefaults(defineProps<{
@ -140,6 +142,11 @@ const style = computed(() => {
.label {
color: #354049 !important;
}
:deep(path),
:deep(rect) {
fill: #354049;
}
}
.blue-white {
@ -153,7 +160,7 @@ const style = computed(() => {
.white-green {
background-color: transparent !important;
border: 1px solid #afb7c1 !important;
border: 1px solid #d8dee3 !important;
.label {
color: var(--c-green-5) !important;

View File

@ -0,0 +1,4 @@
<svg width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.96896 3.46521C3.1732 3.70087 2.59436 4.41876 2.59436 5.26774V13.4991C2.59436 14.5398 3.46409 15.3834 4.53696 15.3834H10.5693C11.4445 15.3834 12.1846 14.8219 12.4275 14.05H11.1578C11.0091 14.1995 10.8004 14.2925 10.5693 14.2925H4.53696L4.51149 14.2921C4.07154 14.279 3.71903 13.929 3.71903 13.4991V5.26774L3.71941 5.24303C3.72617 5.02872 3.82055 4.83579 3.96896 4.69682V3.46521Z" fill="#56606D"/>
<path d="M10.117 1.38342C10.4424 1.38342 10.7545 1.5088 10.9846 1.73198L14.2144 4.86481C14.4445 5.088 14.5738 5.3907 14.5738 5.70634V11.4991C14.5738 12.5398 13.7041 13.3834 12.6312 13.3834H6.59891C5.52604 13.3834 4.65631 12.5398 4.65631 11.4991V3.26779C4.65631 2.22712 5.52604 1.38342 6.59891 1.38342H10.117ZM9.61496 2.47432H6.59891C6.15573 2.47432 5.79483 2.8163 5.78137 3.24307L5.78098 3.26779V11.4991C5.78098 11.929 6.13349 12.2791 6.57344 12.2921L6.59891 12.2925H12.6312C13.0744 12.2925 13.4353 11.9506 13.4488 11.5239L13.4491 11.4991V6.19331L10.1774 6.19339C9.8744 6.19339 9.62738 5.96095 9.61551 5.66987L9.61506 5.64794L9.61496 2.47432ZM12.8689 5.10241L10.7396 3.03713L10.7397 5.10249L12.8689 5.10241Z" fill="#56606D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -12,6 +12,8 @@
--c-light-blue-2: #b3c5ff;
--c-light-blue-3: #89a5ff;
--c-light-blue-4: #537cff;
--c-light-blue-5: #376fff;
--c-light-blue-6: #2683ff;
--c-blue-1: #e6edf7;
--c-blue-2: #d7e8ff;
@ -40,12 +42,15 @@
--c-purple-3: #a18eff;
--c-purple-4: #7b61ff;
--c-grey-0: #f5f6fa;
--c-grey-1: #fafafb;
--c-grey-2: #ebeef1;
--c-grey-3: #d8dee3;
--c-grey-4: #c8d3de;
--c-grey-5: #929fb1;
--c-grey-6: #56606d;
--c-grey-7: #384b65;
--c-grey-8: #1b2533;
--c-white: #ffffff;
--c-black: #000000;