web/satellite: improved project level passphrase experience
Clicking continue in web toggles create project level passphrase which then redirects to project dashboard. Added new create bucket modal. Updated open bucket modal. Updated project dashboard and buckets view to work correctly with no buckets state and no passphrase state. Issue: https://github.com/storj/storj/issues/5455 Change-Id: If6ddac7d3365854a02b2bb8898e4742e9d2c31c1
This commit is contained in:
parent
df9bbcecad
commit
079728f725
@ -711,6 +711,7 @@ async function goToBuckets(): Promise<void> {
|
||||
font-size: 2rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
word-break: break-all;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
margin-bottom: 0.5rem;
|
||||
|
@ -23,6 +23,7 @@
|
||||
<NewBillingAddCouponCodeModal v-if="isNewBillingAddCouponModal" />
|
||||
<CreateProjectPassphraseModal v-if="isCreateProjectPassphraseModal" />
|
||||
<ManageProjectPassphraseModal v-if="isManageProjectPassphraseModal" />
|
||||
<CreateBucketModal v-if="isCreateBucketModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -31,6 +32,7 @@ import { Component, Vue } from 'vue-property-decorator';
|
||||
|
||||
import CreateProjectPromptModal from '@/components/modals/CreateProjectPromptModal.vue';
|
||||
import CreateProjectModal from '@/components/modals/CreateProjectModal.vue';
|
||||
import CreateBucketModal from '@/components/modals/CreateBucketModal.vue';
|
||||
import AddPaymentMethodModal from '@/components/modals/AddPaymentMethodModal.vue';
|
||||
import OpenBucketModal from '@/components/modals/OpenBucketModal.vue';
|
||||
import MFARecoveryCodesModal from '@/components/modals/MFARecoveryCodesModal.vue';
|
||||
@ -58,6 +60,7 @@ import ManageProjectPassphraseModal from '@/components/modals/manageProjectPassp
|
||||
DeleteBucketModal,
|
||||
CreateProjectPromptModal,
|
||||
CreateProjectModal,
|
||||
CreateBucketModal,
|
||||
AddPaymentMethodModal,
|
||||
OpenBucketModal,
|
||||
MFARecoveryCodesModal,
|
||||
@ -216,5 +219,12 @@ export default class AllModals extends Vue {
|
||||
public get isManageProjectPassphraseModal(): boolean {
|
||||
return this.$store.state.appStateModule.appState.isManageProjectPassphraseModalShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if create bucket modal is shown.
|
||||
*/
|
||||
public get isCreateBucketModal(): boolean {
|
||||
return this.$store.state.appStateModule.appState.isCreateBucketModalShown;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
256
web/satellite/src/components/modals/CreateBucketModal.vue
Normal file
256
web/satellite/src/components/modals/CreateBucketModal.vue
Normal file
@ -0,0 +1,256 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<VModal :on-close="closeModal">
|
||||
<template #content>
|
||||
<div class="modal">
|
||||
<CreateBucketIcon class="modal__icon" />
|
||||
<h1 class="modal__title" aria-roledescription="modal-title">
|
||||
Create a Bucket
|
||||
</h1>
|
||||
<p class="modal__info">
|
||||
Buckets are used to store and organize your files. Enter lowercase alphanumeric characters only,
|
||||
no spaces.
|
||||
</p>
|
||||
<VLoader v-if="bucketNamesLoading" width="100px" height="100px" />
|
||||
<VInput
|
||||
v-else
|
||||
:init-value="bucketName"
|
||||
label="Bucket Name"
|
||||
placeholder="Enter bucket name"
|
||||
class="full-input"
|
||||
:error="nameError"
|
||||
@setData="setBucketName"
|
||||
/>
|
||||
<div class="modal__button-container">
|
||||
<VButton
|
||||
label="Cancel"
|
||||
width="100%"
|
||||
height="48px"
|
||||
font-size="14px"
|
||||
:on-press="closeModal"
|
||||
:is-transparent="true"
|
||||
/>
|
||||
<VButton
|
||||
label="Create bucket"
|
||||
width="100%"
|
||||
height="48px"
|
||||
font-size="14px"
|
||||
:on-press="onCreate"
|
||||
:is-disabled="!bucketName"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="isLoading" class="modal__blur">
|
||||
<VLoader class="modal__blur__loader" width="50px" height="50px" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</VModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||
import { RouteConfig } from '@/router';
|
||||
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { useNotify, useRouter, useStore } from '@/utils/hooks';
|
||||
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { Validator } from '@/utils/validation';
|
||||
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
import VInput from '@/components/common/VInput.vue';
|
||||
import VModal from '@/components/common/VModal.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
import CreateBucketIcon from '@/../static/images/buckets/createBucket.svg';
|
||||
|
||||
const store = useStore();
|
||||
const notify = useNotify();
|
||||
const router = useRouter();
|
||||
|
||||
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
|
||||
|
||||
const bucketName = ref<string>('');
|
||||
const nameError = ref<string>('');
|
||||
const bucketNamesLoading = ref<boolean>(true);
|
||||
const isLoading = ref<boolean>(false);
|
||||
|
||||
/**
|
||||
* Returns all bucket names from store.
|
||||
*/
|
||||
const allBucketNames = computed((): string[] => {
|
||||
return store.state.bucketUsageModule.allBucketNames;
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates provided bucket's name and creates a bucket.
|
||||
*/
|
||||
async function onCreate(): Promise<void> {
|
||||
if (isLoading.value) return;
|
||||
|
||||
if (!isBucketNameValid(bucketName.value)) {
|
||||
analytics.errorEventTriggered(AnalyticsErrorEventSource.BUCKET_CREATION_NAME_STEP);
|
||||
return;
|
||||
}
|
||||
|
||||
if (allBucketNames.value.includes(bucketName.value)) {
|
||||
notify.error('Bucket with this name already exists', AnalyticsErrorEventSource.BUCKET_CREATION_NAME_STEP);
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
await store.dispatch(OBJECTS_ACTIONS.CREATE_BUCKET, bucketName.value);
|
||||
await store.dispatch(BUCKET_ACTIONS.FETCH, 1);
|
||||
await store.dispatch(OBJECTS_ACTIONS.SET_FILE_COMPONENT_BUCKET_NAME, bucketName.value);
|
||||
analytics.eventTriggered(AnalyticsEvent.BUCKET_CREATED);
|
||||
analytics.pageVisit(RouteConfig.Buckets.with(RouteConfig.UploadFile).path);
|
||||
await router.push(RouteConfig.Buckets.with(RouteConfig.UploadFile).path);
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
await notify.error(`Unable to fetch buckets. ${error.message}`, AnalyticsErrorEventSource.BUCKET_CREATION_FLOW);
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bucket name value from input to local variable.
|
||||
*/
|
||||
function setBucketName(name: string): void {
|
||||
bucketName.value = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes create bucket modal.
|
||||
*/
|
||||
function closeModal(): void {
|
||||
store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_BUCKET_MODAL_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns validation status of a bucket name.
|
||||
*/
|
||||
function isBucketNameValid(name: string): boolean {
|
||||
switch (true) {
|
||||
case name.length < 3 || name.length > 63:
|
||||
nameError.value = 'Name must be not less than 3 and not more than 63 characters length';
|
||||
return false;
|
||||
case !Validator.bucketName(name):
|
||||
nameError.value = 'Name must contain only lowercase latin characters, numbers, a hyphen or a period';
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async (): Promise<void> => {
|
||||
try {
|
||||
await store.dispatch(BUCKET_ACTIONS.FETCH_ALL_BUCKET_NAMES);
|
||||
bucketName.value = allBucketNames.value.length > 0 ? '' : 'demo-bucket';
|
||||
} catch (error) {
|
||||
await notify.error(error.message, AnalyticsErrorEventSource.BUCKET_CREATION_NAME_STEP);
|
||||
} finally {
|
||||
bucketNamesLoading.value = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.modal {
|
||||
width: 430px;
|
||||
padding: 43px 60px 66px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
font-family: 'font_regular', sans-serif;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
width: calc(100% - 48px);
|
||||
padding: 54px 24px 32px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
max-height: 154px;
|
||||
max-width: 118px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 28px;
|
||||
line-height: 34px;
|
||||
color: #1b2533;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
margin-top: 16px;
|
||||
font-size: 24px;
|
||||
line-height: 31px;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 21px;
|
||||
text-align: center;
|
||||
color: #354049;
|
||||
margin: 20px 0 0;
|
||||
}
|
||||
|
||||
&__button-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 30px;
|
||||
column-gap: 20px;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
margin-top: 20px;
|
||||
column-gap: unset;
|
||||
row-gap: 8px;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
&__blur {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgb(229 229 229 / 20%);
|
||||
border-radius: 8px;
|
||||
z-index: 100;
|
||||
|
||||
&__loader {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.full-input {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
:deep(.label-container) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(.label-container__main__label) {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 14px;
|
||||
color: #56606d;
|
||||
}
|
||||
</style>
|
@ -5,10 +5,19 @@
|
||||
<VModal :on-close="closeModal">
|
||||
<template #content>
|
||||
<div class="modal">
|
||||
<template v-if="isNewEncryptionPassphraseFlowEnabled">
|
||||
<OpenBucketIcon />
|
||||
<h1 class="modal__title">Enter your encryption passphrase</h1>
|
||||
<p class="modal__info">
|
||||
To open a bucket and view your encrypted files, <br>please enter your encryption passphrase.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Icon />
|
||||
<h1 class="modal__title">Open a Bucket</h1>
|
||||
<p class="modal__info">
|
||||
To open a bucket and view your files, please enter the encryption passphrase you saved upon creating this bucket.
|
||||
To open a bucket and view your files, please enter the encryption passphrase you saved upon
|
||||
creating this bucket.
|
||||
</p>
|
||||
<VInput
|
||||
class="modal__input"
|
||||
@ -17,6 +26,7 @@
|
||||
role-description="bucket"
|
||||
:disabled="true"
|
||||
/>
|
||||
</template>
|
||||
<VInput
|
||||
label="Encryption Passphrase"
|
||||
placeholder="Enter a passphrase here"
|
||||
@ -64,14 +74,16 @@ import VInput from '@/components/common/VInput.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
import Icon from '@/../static/images/objects/openBucket.svg';
|
||||
import OpenBucketIcon from '@/../static/images/buckets/openBucket.svg';
|
||||
|
||||
// @vue/component
|
||||
@Component({
|
||||
components: {
|
||||
VInput,
|
||||
VModal,
|
||||
Icon,
|
||||
VButton,
|
||||
Icon,
|
||||
OpenBucketIcon,
|
||||
},
|
||||
})
|
||||
export default class OpenBucketModal extends Vue {
|
||||
|
@ -60,7 +60,7 @@
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { generateMnemonic } from 'bip39';
|
||||
|
||||
import { useNotify, useStore } from '@/utils/hooks';
|
||||
import { useNotify, useRoute, useRouter, useStore } from '@/utils/hooks';
|
||||
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||
import { AccessGrant, EdgeCredentials } from '@/types/accessGrants';
|
||||
@ -68,6 +68,7 @@ import { OBJECTS_ACTIONS, OBJECTS_MUTATIONS } from '@/store/modules/objects';
|
||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||
import { MetaUtils } from '@/utils/meta';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { RouteConfig } from '@/router';
|
||||
|
||||
import VModal from '@/components/common/VModal.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
@ -94,6 +95,8 @@ const FILE_BROWSER_AG_NAME = 'Web file browser API key';
|
||||
|
||||
const store = useStore();
|
||||
const notify = useNotify();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const selectedOption = ref<CreatePassphraseOption>(CreatePassphraseOption.Generate);
|
||||
const activeStep = ref<CreateProjectPassphraseStep>(CreateProjectPassphraseStep.SelectMode);
|
||||
@ -299,6 +302,10 @@ async function onContinue(): Promise<void> {
|
||||
}
|
||||
|
||||
if (activeStep.value === CreateProjectPassphraseStep.Success) {
|
||||
if (route?.name === RouteConfig.OverviewStep.name) {
|
||||
router.push(RouteConfig.ProjectDashboard.path);
|
||||
}
|
||||
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
|
@ -17,12 +17,34 @@
|
||||
/>
|
||||
<div v-if="isEmptyStateShown" class="buckets-table__no-buckets-area">
|
||||
<EmptyBucketIcon class="buckets-table__no-buckets-area__image" />
|
||||
<CreateBucketIcon class="buckets-table__no-buckets-area__small-image" />
|
||||
<h4 class="buckets-table__no-buckets-area__title">There are no buckets in this project</h4>
|
||||
<template v-if="isNewEncryptionPassphraseFlowEnabled">
|
||||
<template v-if="promptForPassphrase">
|
||||
<p class="buckets-table__no-buckets-area__body">Set an encryption passphrase to start uploading files.</p>
|
||||
<VButton
|
||||
label="Set Encryption Passphrase ->"
|
||||
width="234px"
|
||||
height="40px"
|
||||
font-size="14px"
|
||||
:on-press="onSetClick"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="buckets-table__no-buckets-area__body">Create a new bucket to upload files</p>
|
||||
<div class="new-bucket-button" :class="{ disabled: isLoading }" @click="onCreateBucketClick">
|
||||
<WhitePlusIcon class="new-bucket-button__icon" />
|
||||
<p class="new-bucket-button__label">New Bucket</p>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="buckets-table__no-buckets-area__body">Create a new bucket to upload files</p>
|
||||
<div class="new-bucket-button" :class="{ disabled: isLoading }" @click="onNewBucketButtonClick">
|
||||
<WhitePlusIcon class="new-bucket-button__icon" />
|
||||
<p class="new-bucket-button__label">New Bucket</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="isNoSearchResultsShown" class="buckets-table__empty-search">
|
||||
@ -79,10 +101,12 @@ import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/ana
|
||||
import VTable from '@/components/common/VTable.vue';
|
||||
import BucketItem from '@/components/objects/BucketItem.vue';
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import VHeader from '@/components/common/VHeader.vue';
|
||||
|
||||
import WhitePlusIcon from '@/../static/images/common/plusWhite.svg';
|
||||
import EmptyBucketIcon from '@/../static/images/objects/emptyBucket.svg';
|
||||
import CreateBucketIcon from '@/../static/images/buckets/createBucket.svg';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
isLoading?: boolean,
|
||||
@ -147,6 +171,20 @@ const isNewEncryptionPassphraseFlowEnabled = computed((): boolean => {
|
||||
return store.state.appStateModule.isNewEncryptionPassphraseFlowEnabled;
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles set passphrase modal visibility.
|
||||
*/
|
||||
function onSetClick() {
|
||||
store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles create bucket modal visibility.
|
||||
*/
|
||||
function onCreateBucketClick(): void {
|
||||
store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_BUCKET_MODAL_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts bucket creation flow.
|
||||
*/
|
||||
@ -235,14 +273,27 @@ onBeforeUnmount(() => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80px 0;
|
||||
width: 100%;
|
||||
padding: 80px 20px;
|
||||
width: calc(100% - 40px);
|
||||
box-shadow: 0 0 32px rgb(0 0 0 / 4%);
|
||||
background-color: #fff;
|
||||
border-radius: 20px;
|
||||
|
||||
&__image {
|
||||
margin-bottom: 60px;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__small-image {
|
||||
display: none;
|
||||
margin-bottom: 60px;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
@ -251,6 +302,7 @@ onBeforeUnmount(() => {
|
||||
font-size: 18px;
|
||||
line-height: 16px;
|
||||
margin-bottom: 17px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__body {
|
||||
@ -259,6 +311,7 @@ onBeforeUnmount(() => {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,10 +5,26 @@
|
||||
<div class="buckets-view">
|
||||
<div class="buckets-view__title-area">
|
||||
<h1 class="buckets-view__title-area__title" aria-roledescription="title">Buckets</h1>
|
||||
<div class="new-bucket-button" :class="{ disabled: isLoading }" @click="onNewBucketButtonClick">
|
||||
<WhitePlusIcon class="new-bucket-button__icon" />
|
||||
<p class="new-bucket-button__label">New Bucket</p>
|
||||
<template v-if="isNewEncryptionPassphraseFlowEnabled">
|
||||
<VButton
|
||||
v-if="promptForPassphrase"
|
||||
label="Set Encryption Passphrase ->"
|
||||
width="234px"
|
||||
height="40px"
|
||||
font-size="14px"
|
||||
:on-press="onSetClick"
|
||||
/>
|
||||
<div v-else class="buckets-view-button" :class="{ disabled: isLoading }" @click="onCreateBucketClick">
|
||||
<WhitePlusIcon class="buckets-view-button__icon" />
|
||||
<p class="buckets-view-button__label">New Bucket</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="buckets-view-button" :class="{ disabled: isLoading }" @click="onNewBucketButtonClick">
|
||||
<WhitePlusIcon class="buckets-view-button__icon" />
|
||||
<p class="buckets-view-button__label">New Bucket</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="buckets-view__divider" />
|
||||
@ -28,9 +44,11 @@ import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||
import { BucketPage } from '@/types/buckets';
|
||||
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||
|
||||
import EncryptionBanner from '@/components/objects/EncryptionBanner.vue';
|
||||
import BucketsTable from '@/components/objects/BucketsTable.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
|
||||
import WhitePlusIcon from '@/../static/images/common/plusWhite.svg';
|
||||
|
||||
@ -40,6 +58,7 @@ import WhitePlusIcon from '@/../static/images/common/plusWhite.svg';
|
||||
WhitePlusIcon,
|
||||
BucketsTable,
|
||||
EncryptionBanner,
|
||||
VButton,
|
||||
},
|
||||
})
|
||||
export default class BucketsView extends Vue {
|
||||
@ -102,6 +121,20 @@ export default class BucketsView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles create project passphrase modal visibility.
|
||||
*/
|
||||
public onSetClick(): void {
|
||||
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles create bucket modal visibility.
|
||||
*/
|
||||
public onCreateBucketClick(): void {
|
||||
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_BUCKET_MODAL_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts bucket creation flow.
|
||||
*/
|
||||
@ -125,6 +158,20 @@ export default class BucketsView extends Vue {
|
||||
return this.$store.state.bucketUsageModule.page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if new encryption passphrase flow is enabled.
|
||||
*/
|
||||
public get isNewEncryptionPassphraseFlowEnabled(): boolean {
|
||||
return this.$store.state.appStateModule.isNewEncryptionPassphraseFlowEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if user should be prompt for passphrase.
|
||||
*/
|
||||
public get promptForPassphrase(): boolean {
|
||||
return this.$store.state.objectsModule.promptForPassphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected project id from store.
|
||||
*/
|
||||
@ -135,7 +182,7 @@ export default class BucketsView extends Vue {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.new-bucket-button {
|
||||
.buckets-view-button {
|
||||
padding: 0 15px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
@ -174,6 +221,7 @@ export default class BucketsView extends Vue {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__title {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
|
@ -95,14 +95,29 @@ export default class OverviewStep extends Vue {
|
||||
* Redirects to buckets page.
|
||||
*/
|
||||
public onUploadInBrowserClick(): void {
|
||||
if (this.isNewEncryptionPassphraseFlowEnabled) {
|
||||
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$router.push(RouteConfig.Buckets.path).catch(() => {return; });
|
||||
this.analytics.linkEventTriggered(AnalyticsEvent.PATH_SELECTED, 'Continue in Browser');
|
||||
this.analytics.pageVisit(RouteConfig.Buckets.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns satellite name.
|
||||
*/
|
||||
private get satelliteName(): string {
|
||||
return this.$store.state.appStateModule.satelliteName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if new encryption passphrase flow is enabled.
|
||||
*/
|
||||
private get isNewEncryptionPassphraseFlowEnabled(): boolean {
|
||||
return this.$store.state.appStateModule.isNewEncryptionPassphraseFlowEnabled;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -0,0 +1,158 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<div class="dashboard-header">
|
||||
<VLoader
|
||||
v-if="loading"
|
||||
class="dashboard-header__loader"
|
||||
width="100px"
|
||||
height="100px"
|
||||
/>
|
||||
<template v-else>
|
||||
<template v-if="promptForPassphrase">
|
||||
<p class="dashboard-header__subtitle">
|
||||
Set an encryption passphrase <br>to start uploading files.
|
||||
</p>
|
||||
<VButton
|
||||
label="Set Encryption Passphrase ->"
|
||||
width="234px"
|
||||
height="48px"
|
||||
font-size="14px"
|
||||
:on-press="onSetClick"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="!promptForPassphrase && !bucketsPage.buckets.length && !bucketsPage.search">
|
||||
<p class="dashboard-header__subtitle">
|
||||
Create a bucket to start <br>uploading data in your project.
|
||||
</p>
|
||||
<VButton
|
||||
label="Create a bucket ->"
|
||||
width="160px"
|
||||
height="48px"
|
||||
font-size="14px"
|
||||
:on-press="onCreateBucketClick"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="dashboard-header__subtitle" aria-roledescription="with-usage-title">
|
||||
Your
|
||||
<span class="dashboard-header__subtitle__value">{{ limits.objectCount }} objects</span>
|
||||
are stored <br>in
|
||||
<span class="dashboard-header__subtitle__value">{{ limits.segmentCount }} segments</span>
|
||||
around the world
|
||||
</p>
|
||||
<p class="dashboard-header__limits">
|
||||
<span class="dashboard-header__limits--bold">Storage Limit</span>
|
||||
per month: {{ limits.storageLimit | bytesToBase10String }} |
|
||||
<span class="dashboard-header__limits--bold">Bandwidth Limit</span>
|
||||
per month: {{ limits.bandwidthLimit | bytesToBase10String }}
|
||||
</p>
|
||||
<VButton
|
||||
label="Upload"
|
||||
width="100px"
|
||||
height="40px"
|
||||
:on-press="onUploadClick"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useRouter, useStore } from '@/utils/hooks';
|
||||
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||
import { BucketPage } from '@/types/buckets';
|
||||
import { ProjectLimits } from '@/types/projects';
|
||||
import { RouteConfig } from '@/router';
|
||||
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import VLoader from '@/components/common/VLoader.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
loading?: boolean;
|
||||
}>(), {
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* Indicates if user should be prompt for passphrase.
|
||||
*/
|
||||
const promptForPassphrase = computed((): boolean => {
|
||||
return store.state.objectsModule.promptForPassphrase;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns current limits from store.
|
||||
*/
|
||||
const limits = computed((): ProjectLimits => {
|
||||
return store.state.projectsModule.currentLimits;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns fetched buckets page from store.
|
||||
*/
|
||||
const bucketsPage = computed((): BucketPage => {
|
||||
return store.state.bucketUsageModule.page;
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles create project passphrase modal visibility.
|
||||
*/
|
||||
function onSetClick() {
|
||||
store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles create bucket modal visibility.
|
||||
*/
|
||||
function onCreateBucketClick() {
|
||||
store.commit(APP_STATE_MUTATIONS.TOGGLE_CREATE_BUCKET_MODAL_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to bucket management screen.
|
||||
*/
|
||||
function onUploadClick() {
|
||||
router.push(RouteConfig.Buckets.with(RouteConfig.BucketsManagement).path);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dashboard-header {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
|
||||
&__loader {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
letter-spacing: -0.02em;
|
||||
color: #000;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&__value {
|
||||
text-decoration: underline;
|
||||
text-underline-position: under;
|
||||
text-decoration-color: var(--c-green-3);
|
||||
}
|
||||
}
|
||||
|
||||
&__limits {
|
||||
font-size: 14px;
|
||||
margin: 11px 0 16px;
|
||||
|
||||
&--bold {
|
||||
font-family: 'font_bold', sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -7,6 +7,11 @@
|
||||
<p class="project-dashboard__message">
|
||||
Expect a delay of a few hours between network activity and the latest dashboard stats.
|
||||
</p>
|
||||
<DashboardFunctionalHeader
|
||||
v-if="isNewEncryptionPassphraseFlowEnabled"
|
||||
:loading="isDataFetching || areBucketsFetching"
|
||||
/>
|
||||
<template v-else>
|
||||
<VLoader v-if="isDataFetching" class="project-dashboard__loader" width="100px" height="100px" />
|
||||
<p v-if="!isDataFetching && limits.objectCount" class="project-dashboard__subtitle" aria-roledescription="with-usage-title">
|
||||
Your
|
||||
@ -35,6 +40,7 @@
|
||||
:on-press="onUploadClick"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<div class="project-dashboard__stats-header">
|
||||
<h2 class="project-dashboard__stats-header__title">Project Stats</h2>
|
||||
<div class="project-dashboard__stats-header__buttons">
|
||||
@ -195,6 +201,7 @@ import VLoader from '@/components/common/VLoader.vue';
|
||||
import InfoContainer from '@/components/project/newProjectDashboard/InfoContainer.vue';
|
||||
import StorageChart from '@/components/project/newProjectDashboard/StorageChart.vue';
|
||||
import BandwidthChart from '@/components/project/newProjectDashboard/BandwidthChart.vue';
|
||||
import DashboardFunctionalHeader from '@/components/project/newProjectDashboard/DashboardFunctionalHeader.vue';
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import DateRangeSelection from '@/components/project/newProjectDashboard/DateRangeSelection.vue';
|
||||
import VInfo from '@/components/common/VInfo.vue';
|
||||
@ -218,6 +225,7 @@ import InfoIcon from '@/../static/images/project/infoIcon.svg';
|
||||
InfoIcon,
|
||||
BucketsTable,
|
||||
EncryptionBanner,
|
||||
DashboardFunctionalHeader,
|
||||
},
|
||||
})
|
||||
export default class NewProjectDashboard extends Vue {
|
||||
@ -353,13 +361,6 @@ export default class NewProjectDashboard extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens add payment method modal.
|
||||
*/
|
||||
public togglePMModal(): void {
|
||||
this.$store.commit(APP_STATE_MUTATIONS.TOGGLE_IS_ADD_PM_MODAL_SHOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if charts date picker is shown.
|
||||
*/
|
||||
@ -430,6 +431,13 @@ export default class NewProjectDashboard extends Vue {
|
||||
return this.$store.state.projectsModule.chartDataBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if new encryption passphrase flow is enabled.
|
||||
*/
|
||||
public get isNewEncryptionPassphraseFlowEnabled(): boolean {
|
||||
return this.$store.state.appStateModule.isNewEncryptionPassphraseFlowEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats value to needed form and returns it.
|
||||
*/
|
||||
|
@ -31,6 +31,7 @@ class ViewsState {
|
||||
public isSuccessfulPasswordResetShown = false,
|
||||
public isCreateProjectPromptModalShown = false,
|
||||
public isCreateProjectModalShown = false,
|
||||
public isCreateBucketModalShown = false,
|
||||
public isAddPMModalShown = false,
|
||||
public isOpenBucketModalShown = false,
|
||||
public isMFARecoveryModalShown = false,
|
||||
@ -151,6 +152,9 @@ export const appStateModule = {
|
||||
[APP_STATE_MUTATIONS.TOGGLE_MANAGE_PROJECT_PASSPHRASE_MODAL_SHOWN](state: State): void {
|
||||
state.appState.isManageProjectPassphraseModalShown = !state.appState.isManageProjectPassphraseModalShown;
|
||||
},
|
||||
[APP_STATE_MUTATIONS.TOGGLE_CREATE_BUCKET_MODAL_SHOWN](state: State): void {
|
||||
state.appState.isCreateBucketModalShown = !state.appState.isCreateBucketModalShown;
|
||||
},
|
||||
[APP_STATE_MUTATIONS.TOGGLE_MFA_RECOVERY_MODAL_SHOWN](state: State): void {
|
||||
state.appState.isMFARecoveryModalShown = !state.appState.isMFARecoveryModalShown;
|
||||
},
|
||||
|
@ -46,6 +46,7 @@ export const APP_STATE_MUTATIONS = {
|
||||
TOGGLE_NEW_FOLDER_MODAL_SHOWN: 'TOGGLE_NEW_FOLDER_MODAL_SHOWN',
|
||||
TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN: 'TOGGLE_CREATE_PROJECT_PASSPHRASE_MODAL_SHOWN',
|
||||
TOGGLE_MANAGE_PROJECT_PASSPHRASE_MODAL_SHOWN: 'TOGGLE_MANAGE_PROJECT_PASSPHRASE_MODAL_SHOWN',
|
||||
TOGGLE_CREATE_BUCKET_MODAL_SHOWN: 'TOGGLE_CREATE_BUCKET_MODAL_SHOWN',
|
||||
SHOW_DELETE_PAYMENT_METHOD_POPUP: 'SHOW_DELETE_PAYMENT_METHOD_POPUP',
|
||||
SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP: 'SHOW_SET_DEFAULT_PAYMENT_METHOD_POPUP',
|
||||
CLOSE_ALL: 'CLOSE_ALL',
|
||||
|
5
web/satellite/static/images/buckets/createBucket.svg
Normal file
5
web/satellite/static/images/buckets/createBucket.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="112" height="113" viewBox="0 0 112 113" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M46.04 1H65.3698C80.7857 1 86.7521 2.72263 92.5841 5.86948C98.4422 9.0304 103.035 13.6635 106.17 19.5763C106.17 19.577 106.171 19.5778 106.171 19.5785L106.411 20.0376C106.412 20.0383 106.412 20.0389 106.412 20.0396C109.339 25.7076 110.957 31.7258 111 46.4499C111 46.4508 111 46.4516 111 46.4525L111 65.9534C111 81.5098 109.292 87.5337 106.171 93.4215C103.037 99.3345 98.4442 103.968 92.5865 107.129C92.5857 107.13 92.5849 107.13 92.5841 107.131L92.1292 107.373C92.1285 107.373 92.1278 107.374 92.1271 107.374C86.5129 110.324 80.5522 111.957 65.9611 112C65.9602 112 65.9594 112 65.9585 112L46.6303 112C31.2143 112 25.2479 110.277 19.4159 107.131C13.5578 103.97 8.96468 99.3365 5.82993 93.4236C5.82956 93.4229 5.82919 93.4222 5.82881 93.4215L5.58858 92.9624C5.58822 92.9617 5.58786 92.961 5.58751 92.9603C2.66136 87.2922 1.04304 81.2739 1 66.5489V47.0466C1 31.4902 2.70786 25.4663 5.82881 19.5785C8.96317 13.6654 13.5558 9.03204 19.4135 5.87075C19.4143 5.87033 19.4151 5.8699 19.4159 5.86948L19.8708 5.62718C19.8716 5.62674 19.8725 5.6263 19.8733 5.62586C25.4875 2.67566 31.4484 1.04342 46.04 1Z" fill="white" stroke="#EBEEF1" stroke-width="2"/>
|
||||
<path d="M30.833 39.3889L56.4997 44.5222L82.1663 39.3889L73.6108 84.3056L67.1941 86.4445L56.4997 88.1556L45.8052 86.4445L39.3886 84.3056L30.833 39.3889Z" fill="#D7E8FF"/>
|
||||
<path d="M87.9628 50.2829C93.9821 56.3022 94.0371 66.0264 88.0608 72.0028C85.6392 74.4243 82.6022 75.8557 79.4458 76.3012L78.3622 82.4397C78.2826 87.2558 68.4258 91.1501 56.2781 91.1501C44.2034 91.1501 34.392 87.3024 34.197 82.5264L34.1952 82.4397L25.8913 35.3933C25.7978 35.0606 25.738 34.7238 25.7132 34.3835L25.7002 34.3078L25.7083 34.3082C25.7029 34.214 25.7002 34.1194 25.7002 34.0246C25.7002 27.3008 39.3904 21.8501 56.2781 21.8501C73.1659 21.8501 86.8561 27.3008 86.8561 34.0246C86.8561 34.1194 86.8534 34.214 86.8479 34.3082L86.8561 34.3078L86.8427 34.3886C86.8179 34.725 86.7588 35.058 86.6668 35.3871L84.6262 46.9461L87.9628 50.2829ZM79.156 42.1026C73.5544 44.6156 65.3808 46.1992 56.2781 46.1992C47.1758 46.1992 39.0024 44.6157 33.4009 42.1028L39.4201 76.2109L40.4042 81.4335L40.4954 81.5043C41.0101 81.8902 41.8067 82.3253 42.8411 82.7445L43.0512 82.8282C46.3855 84.132 51.1367 84.9213 56.2781 84.9213C61.4492 84.9213 66.2248 84.1228 69.5592 82.8069C70.7611 82.3326 71.657 81.8343 72.1829 81.4098L72.2257 81.3746L73.1918 75.8991C70.7615 75.2311 68.4587 73.9585 66.5226 72.0836L66.3409 71.9048L53.8261 59.39C52.6099 58.1737 52.6099 56.2018 53.8261 54.9855C55.0127 53.799 56.9186 53.77 58.1403 54.8987L58.2306 54.9855L70.7454 67.5003C71.7792 68.5342 72.9934 69.2737 74.2826 69.7188L79.156 42.1026ZM83.3051 54.4337L80.6304 69.5889C81.7344 69.1515 82.7666 68.488 83.6563 67.5983C87.1468 64.1078 87.1564 58.4225 83.6853 54.8168L83.5583 54.6874L83.3051 54.4337ZM56.2781 28.0789C48.8056 28.0789 41.8756 29.246 36.9604 31.203C34.8483 32.0439 33.2769 32.9699 32.3726 33.8213C32.3149 33.8755 32.2623 33.9272 32.2146 33.9762L32.1691 34.0241L32.2513 34.1104L32.3726 34.228C33.2769 35.0794 34.8483 36.0054 36.9604 36.8463C41.8756 38.8033 48.8056 39.9704 56.2781 39.9704C63.7507 39.9704 70.6807 38.8033 75.5959 36.8463C77.708 36.0054 79.2793 35.0794 80.1837 34.228C80.2413 34.1738 80.2939 34.1221 80.3417 34.0731L80.3877 34.0241L80.305 33.9389L80.1837 33.8213C79.2793 32.9699 77.708 32.0439 75.5959 31.203C70.6807 29.246 63.7507 28.0789 56.2781 28.0789Z" fill="#0149FF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
7
web/satellite/static/images/buckets/openBucket.svg
Normal file
7
web/satellite/static/images/buckets/openBucket.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg width="112" height="113" viewBox="0 0 112 113" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M46.0385 0H65.3698C80.8738 0 87.0253 1.73377 93.059 4.98942C99.0926 8.24507 103.828 13.0226 107.055 19.1102L107.3 19.5788C110.328 25.444 111.957 31.6399 112 46.4496V65.9534C112 81.5959 110.282 87.8023 107.055 93.8898C103.828 99.9774 99.0926 104.755 93.059 108.011L92.5944 108.258C86.7812 111.313 80.6401 112.956 65.9615 113H46.6303C31.1262 113 24.9747 111.266 18.941 108.011C12.9074 104.755 8.17211 99.9774 4.94527 93.8898L4.70003 93.4212C1.67181 87.556 0.0431812 81.3601 0 66.5504V47.0466C0 31.4041 1.71842 25.1977 4.94527 19.1102C8.17211 13.0226 12.9074 8.24507 18.941 4.98942L19.4056 4.74199C25.2188 1.68673 31.3599 0.0435667 46.0385 0Z" fill="#EBEEF1"/>
|
||||
<path d="M56.4626 69.1075C68.2203 69.1075 77.7519 59.491 77.7519 47.6285C77.7519 35.7659 68.2203 26.1494 56.4626 26.1494C44.7049 26.1494 35.1733 35.7659 35.1733 47.6285C35.1733 59.491 44.7049 69.1075 56.4626 69.1075Z" fill="#FFC600"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.9999 20.5449C71.5917 20.5449 84.2314 33.0881 84.2314 48.5609C84.2314 64.0337 71.5917 76.5769 55.9999 76.5769C41.3478 76.5769 29.3027 65.5001 27.9041 51.3245C28.1184 46.3636 28.585 42.5895 29.2916 39.4614C33.0977 28.4553 43.6179 20.5449 55.9999 20.5449ZM55.9999 28.9497C44.9784 28.9497 36.099 37.7613 36.099 48.5609C36.099 59.3605 44.9784 68.1721 55.9999 68.1721C67.0214 68.1721 75.9008 59.3605 75.9008 48.5609C75.9008 37.7613 67.0214 28.9497 55.9999 28.9497Z" fill="#FF458B"/>
|
||||
<path d="M84.2313 48.5625H27.7688V91.5204H84.2313V48.5625Z" fill="#0149FF"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M56.4626 59.7686C59.5298 59.7686 62.0163 62.111 62.0163 65.0005C62.0163 67.1876 60.5917 69.0613 58.5691 69.8429L58.5692 75.6447H54.356L54.3561 69.8429C52.3335 69.0613 50.9089 67.1876 50.9089 65.0005C50.9089 62.111 53.3954 59.7686 56.4626 59.7686Z" fill="#0218A7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
Loading…
Reference in New Issue
Block a user