Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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: #5455 Change-Id: If6ddac7d3365854a02b2bb8898e4742e9d2c31c1
- Loading branch information
1 parent
df9bbce
commit 079728f
Showing
14 changed files
with
644 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
256 changes: 256 additions & 0 deletions
256
web/satellite/src/components/modals/CreateBucketModal.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.