web/satellite: third step on new access grant flow

Added third step of new access grant flow where user selects further passphrase option from this list:
- use existing passphrase
- set my project passphrase
- generate new passphrase
- enter new passphrase

Note: In case of 'use existing passphrase' option access grant will be generated and user will be redirected to success screen (not implemented yet).

Change-Id: Idc238bb469f3e7a87a6523783cee4963bfe0445d
This commit is contained in:
Vitalii 2023-02-14 15:44:51 +02:00
parent 00021e6c75
commit 6f12ba59ba
6 changed files with 415 additions and 24 deletions

View File

@ -32,23 +32,37 @@
:not-after-label="notAfterLabel"
:on-set-not-after-label="setNotAfterLabel"
/>
<AccessEncryptionStep
v-if="step === CreateAccessStep.AccessEncryption"
:on-back="() => setStep(CreateAccessStep.ChoosePermission)"
:on-continue="setStepBasedOnPassphraseOption"
:passphrase-option="passphraseOption"
:set-option="setPassphraseOption"
/>
</div>
</template>
</VModal>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { computed, onMounted, ref } from 'vue';
import { useNotify, useRoute, useRouter, useStore } from '@/utils/hooks';
import { RouteConfig } from '@/router';
import { AccessType, CreateAccessStep, Permission, STEP_ICON_AND_TITLE } from '@/types/createAccessGrant';
import {
AccessType,
CreateAccessStep,
PassphraseOption,
Permission,
STEP_ICON_AND_TITLE,
} from '@/types/createAccessGrant';
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import VModal from '@/components/common/VModal.vue';
import CreateNewAccessStep from '@/components/accessGrants/newCreateFlow/steps/CreateNewAccessStep.vue';
import ChoosePermissionStep from '@/components/accessGrants/newCreateFlow/steps/ChoosePermissionStep.vue';
import AccessEncryptionStep from '@/components/accessGrants/newCreateFlow/steps/AccessEncryptionStep.vue';
const router = useRouter();
const route = useRoute();
@ -62,10 +76,28 @@ const initPermissions = [
Permission.List,
];
/**
* Indicates if user has to be prompt to enter project passphrase.
*/
const isPromptForPassphrase = computed((): boolean => {
return store.state.objectsModule.promptForPassphrase;
});
/**
* Returns passphrase from store.
*/
const storedPassphrase = computed((): string => {
return store.state.objectsModule.passphrase;
});
const step = ref<CreateAccessStep>(CreateAccessStep.CreateNewAccess);
const selectedAccessTypes = ref<AccessType[]>([]);
const selectedPermissions = ref<Permission[]>(initPermissions);
const selectedBuckets = ref<string[]>([]);
const passphraseOption = ref<PassphraseOption>(
isPromptForPassphrase.value ? PassphraseOption.SetMyProjectPassphrase : PassphraseOption.UseExistingPassphrase,
);
const passphrase = ref<string>(storedPassphrase.value);
const accessName = ref<string>('');
const notAfter = ref<Date | undefined>(undefined);
const notAfterLabel = ref<string>('No end date');
@ -120,6 +152,20 @@ function selectAccessType(type: AccessType) {
}
}
/**
* Sets passphrase option.
*/
function setPassphraseOption(option: PassphraseOption): void {
passphraseOption.value = option;
}
/**
* Sets passphrase.
*/
function setPassphrase(value: string): void {
passphrase.value = value;
}
/**
* Sets not after (end date) caveat.
*/
@ -212,12 +258,32 @@ function setAccessName(value: string): void {
}
/**
* Sets current step to be 'Choose permission'.
* Sets current step.
*/
function setStep(stepArg: CreateAccessStep): void {
step.value = stepArg;
}
/**
* Sets next step depending on selected passphrase option.
*/
function setStepBasedOnPassphraseOption(): void {
switch (passphraseOption.value) {
case PassphraseOption.SetMyProjectPassphrase:
step.value = CreateAccessStep.EnterMyPassphrase;
break;
case PassphraseOption.EnterNewPassphrase:
step.value = CreateAccessStep.EnterNewPassphrase;
break;
case PassphraseOption.GenerateNewPassphrase:
step.value = CreateAccessStep.PassphraseGenerated;
break;
default:
// TODO: generate access and redirect to access created.
step.value = CreateAccessStep.AccessCreated;
}
}
/**
* Closes create access grant flow.
*/

View File

@ -2,45 +2,55 @@
// See LICENSE for copying information.
<template>
<div class="wrapper">
<component :is="iconAndTitle.icon" class="wrapper__icon" />
<div class="wrapper__functional">
<h2 class="wrapper__functional__title">{{ iconAndTitle.title }}</h2>
<slot name="functional" />
<div class="section">
<div class="section__wrapper">
<component :is="iconAndTitle.icon" class="section__wrapper__icon" />
<div class="section__wrapper__functional">
<h2 class="section__wrapper__functional__title">{{ iconAndTitle.title }}</h2>
<slot name="functional" />
</div>
</div>
<slot v-if="slots.info" name="info" />
</div>
</template>
<script setup lang="ts">
import { useSlots } from 'vue';
import { IconAndTitle } from '@/types/createAccessGrant';
const props = defineProps<{
iconAndTitle: IconAndTitle;
}>();
const slots = useSlots();
</script>
<style scoped lang="scss">
.wrapper {
display: flex;
align-items: flex-start;
.section {
padding: 16px 0;
border-bottom: 1px solid #ebeef1;
&__icon {
min-width: 40px;
}
&__wrapper {
display: flex;
align-items: flex-start;
&__functional {
margin-left: 16px;
width: calc(100% - 56px);
&__icon {
min-width: 40px;
}
&__title {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: #000;
text-align: left;
margin-bottom: 8px;
&__functional {
margin-left: 16px;
width: calc(100% - 56px);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: #000;
text-align: left;
margin-bottom: 8px;
}
}
}
}

View File

@ -0,0 +1,89 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="radio">
<input :id="`checkbox${label}`" :checked="checked" type="radio" @change="onCheck">
<label class="radio__label" :for="`checkbox${label}`">{{ label }}</label>
<VInfo class="radio__info">
<template #icon>
<InfoIcon class="radio__info__icon" />
</template>
<template #message>
<p class="radio__info__message">{{ info }}</p>
</template>
</VInfo>
</div>
</template>
<script setup lang="ts">
import VInfo from '@/components/common/VInfo.vue';
import InfoIcon from '@/../static/images/accessGrants/newCreateFlow/info.svg';
const props = defineProps<{
checked: boolean;
label: string;
onCheck: () => void;
info: string;
}>();
</script>
<style scoped lang="scss">
.radio {
display: flex;
align-items: center;
font-family: 'font_regular', sans-serif;
input {
cursor: pointer;
height: 16px;
width: 16px;
}
&__label {
margin-left: 8px;
font-size: 14px;
line-height: 20px;
color: var(--c-black);
cursor: pointer;
}
&__info {
margin-left: 8px;
max-height: 16px;
&__icon {
cursor: pointer;
}
&__message {
color: #fff;
}
}
}
:deep(.info__box) {
width: 270px;
left: calc(50% - 135px);
top: unset;
bottom: 15px;
cursor: default;
filter: none;
transform: rotate(-180deg);
}
:deep(.info__box__message) {
background: var(--c-grey-6);
border-radius: 4px;
padding: 10px 8px;
transform: rotate(-180deg);
}
:deep(.info__box__arrow) {
background: var(--c-grey-6);
width: 10px;
height: 10px;
margin-bottom: -3px;
}
</style>

View File

@ -0,0 +1,211 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="encryption">
<ContainerWithIcon :icon-and-title="FUNCTIONAL_CONTAINER_ICON_AND_TITLE[FunctionalContainer.EncryptionPassphrase]">
<template #functional>
<div class="encryption__radios">
<Radio
v-if="!isPromptForPassphrase"
:checked="isSelectedOption(_PassphraseOption.UseExistingPassphrase)"
:on-check="() => setOption(_PassphraseOption.UseExistingPassphrase)"
label="Use the current passphrase"
info="Create this access with the same passphrase you use for this project.
This allows you to manage existing data you have uploaded with the same passphrase."
/>
<Radio
v-else
:checked="isSelectedOption(_PassphraseOption.SetMyProjectPassphrase)"
:on-check="() => setOption(_PassphraseOption.SetMyProjectPassphrase)"
label="Enter my project passphrase"
info="You will enter your encryption passphrase on the next step. Make sure it's the same one
you use for this project. This allows you to manage existing data you have uploaded with the
same passphrase."
/>
<div class="encryption__radios__advanced" @click="toggleAdvanced">
<h2 class="encryption__radios__advanced__label">Advanced</h2>
<ChevronIcon
class="encryption__radios__advanced__chevron"
:class="{'encryption__radios__advanced__chevron--up': advancedShown}"
/>
</div>
<template v-if="advancedShown">
<Radio
:checked="isSelectedOption(_PassphraseOption.EnterNewPassphrase)"
:on-check="() => setOption(_PassphraseOption.EnterNewPassphrase)"
label="Enter a new passphrase"
info="Create this access with a new encryption passphrase that you can enter on the next step.
The access will not be able to manage any existing data."
/>
<Radio
:checked="isSelectedOption(_PassphraseOption.GenerateNewPassphrase)"
:on-check="() => setOption(_PassphraseOption.GenerateNewPassphrase)"
label="Generate 12-word passphrase"
info="Create this access with a new encryption passphrase that will be generated for you on
the next step. The access will not be able to manage any existing data."
/>
</template>
</div>
</template>
<template #info>
<div v-if="advancedShown" class="encryption__warning-container">
<OrangeWarningIcon class="encryption__warning-container__icon" />
<div>
<p class="encryption__warning-container__message">
<b>Warning.</b> Creating a new passphrase for this access will prevent it from decrypting
data that has already been uploaded with the current passphrase.
</p>
<br>
<p class="encryption__warning-container__disclaimer">
Proceed only if you understand
</p>
</div>
</div>
</template>
</ContainerWithIcon>
<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"
/>
</template>
</ButtonsContainer>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import {
FUNCTIONAL_CONTAINER_ICON_AND_TITLE,
FunctionalContainer,
PassphraseOption,
} from '@/types/createAccessGrant';
import { useStore } from '@/utils/hooks';
import ContainerWithIcon from '@/components/accessGrants/newCreateFlow/components/ContainerWithIcon.vue';
import ButtonsContainer from '@/components/accessGrants/newCreateFlow/components/ButtonsContainer.vue';
import Radio from '@/components/accessGrants/newCreateFlow/components/Radio.vue';
import VButton from '@/components/common/VButton.vue';
import ChevronIcon from '@/../static/images/accessGrants/newCreateFlow/chevron.svg';
import OrangeWarningIcon from '@/../static/images/accessGrants/newCreateFlow/orangeWarning.svg';
const props = defineProps<{
passphraseOption: PassphraseOption;
setOption: (option: PassphraseOption) => void;
onBack: () => void;
onContinue: () => void;
}>();
const store = useStore();
const advancedShown = ref<boolean>(false);
// We do this because imported enum is not directly accessible in template.
const _PassphraseOption = PassphraseOption;
/**
* Indicates if user has to be prompt to enter project passphrase.
*/
const isPromptForPassphrase = computed((): boolean => {
return store.state.objectsModule.promptForPassphrase;
});
/**
* Toggles advanced options visibility.
*/
function toggleAdvanced(): void {
advancedShown.value = !advancedShown.value;
}
/**
* Indicates if option is selected in root component.
*/
function isSelectedOption(option: PassphraseOption): boolean {
return props.passphraseOption === option;
}
</script>
<style lang="scss" scoped>
.encryption {
font-family: 'font_regular', sans-serif;
&__radios {
display: flex;
flex-direction: column;
row-gap: 16px;
&__advanced {
display: flex;
align-items: center;
&__label {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: #000;
text-align: left;
cursor: pointer;
}
&__chevron {
transition: transform 0.3s;
margin-left: 8px;
cursor: pointer;
&--up {
transform: rotate(180deg);
}
}
}
}
&__warning-container {
background: #fec;
border: 1px solid #ffd78a;
box-shadow: 0 7px 20px rgb(0 0 0 / 15%);
border-radius: 10px;
padding: 16px;
display: flex;
align-items: flex-start;
margin-top: 16px;
&__icon {
min-width: 32px;
margin-right: 16px;
}
&__message {
font-size: 14px;
line-height: 20px;
color: #000;
text-align: left;
}
&__disclaimer {
font-weight: bold;
font-size: 14px;
line-height: 22px;
color: #000;
font-style: italic;
text-align: left;
}
}
}
</style>

View File

@ -28,12 +28,20 @@ export enum AccessType {
AccessGrant = 'accessGrant',
}
export enum PassphraseOption {
UseExistingPassphrase = 'useExistingPassphrase',
SetMyProjectPassphrase = 'setMyProjectPassphrase',
GenerateNewPassphrase = 'generateNewPassphrase',
EnterNewPassphrase = 'enterNewPassphrase',
}
export enum CreateAccessStep {
CreateNewAccess = 'createNewAccess',
ChoosePermission = 'choosePermission',
EncryptionInfo = 'encryptionInfo',
AccessEncryption = 'accessEncryption',
PassphraseGenerated = 'passphraseGenerated',
EnterMyPassphrase = 'enterMyPassphrase',
EnterNewPassphrase = 'enterNewPassphrase',
AccessCreated = 'accessCreated',
CredentialsCreated = 'credentialsCreated',
@ -68,6 +76,10 @@ export const STEP_ICON_AND_TITLE: Record<CreateAccessStep, IconAndTitle> = {
icon: PassphraseGeneratedIcon,
title: 'Passphrase generated',
},
[CreateAccessStep.EnterMyPassphrase]: {
icon: AccessEncryptionIcon,
title: 'Enter my passphrase',
},
[CreateAccessStep.EnterNewPassphrase]: {
icon: AccessEncryptionIcon,
title: 'Enter a new passphrase',

View File

@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.0001 1.6001C23.9529 1.6001 30.4 8.0472 30.4 16.0001C30.4 23.953 23.9529 30.4001 16.0001 30.4001C8.04718 30.4001 1.6001 23.953 1.6001 16.0001C1.6001 8.0472 8.04718 1.6001 16.0001 1.6001ZM16.0001 4.2401C9.5052 4.2401 4.24009 9.50523 4.24009 16.0001C4.24009 22.495 9.5052 27.7601 16.0001 27.7601C22.4949 27.7601 27.76 22.495 27.76 16.0001C27.76 9.50523 22.4949 4.2401 16.0001 4.2401ZM17.2 9.40083V16.9937C17.2 17.7227 16.6091 18.3137 15.8801 18.3137C15.1688 18.3137 14.589 17.7512 14.5611 17.0468L14.5601 16.9937V9.48867C14.5601 8.75225 15.144 8.14851 15.8801 8.12402C16.5852 8.10055 17.1759 8.65318 17.1993 9.35835C17.1998 9.3725 17.2 9.38666 17.2 9.40083ZM17.2 22.1208V22.2737C17.2 23.0027 16.6091 23.5937 15.8801 23.5937C15.1688 23.5937 14.589 23.0312 14.5611 22.3268L14.5601 22.2737V22.2087C14.5601 21.4722 15.144 20.8685 15.8801 20.844C16.5852 20.8206 17.1759 21.3732 17.1993 22.0783C17.1998 22.0925 17.2 22.1067 17.2 22.1208Z" fill="#FF8A00"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB