web/satellite: rework delete access grant modal

Reworked delete access grant modal to be more simple.

Issue:
https://github.com/storj/storj/issues/5775

Change-Id: I415e75458736522f17914f252be531db5a2313f7
This commit is contained in:
Vitalii 2023-04-27 15:31:07 +03:00 committed by Storj Robot
parent 978b714128
commit 30dee52256
6 changed files with 188 additions and 274 deletions

View File

@ -152,10 +152,6 @@
</span>
</div>
</div>
<ConfirmDeletePopup
v-if="isDeleteClicked"
@close="onClearSelection"
/>
<router-view />
</div>
</template>
@ -172,9 +168,10 @@ import { AccessType } from '@/types/createAccessGrant';
import { useNotify, useRouter } from '@/utils/hooks';
import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useAppStore } from '@/store/modules/appStore';
import { MODALS } from '@/utils/constants/appStatePopUps';
import AccessGrantsItem from '@/components/accessGrants/AccessGrantsItem.vue';
import ConfirmDeletePopup from '@/components/accessGrants/ConfirmDeletePopup.vue';
import VButton from '@/components/common/VButton.vue';
import VLoader from '@/components/common/VLoader.vue';
import VHeader from '@/components/common/VHeader.vue';
@ -188,12 +185,12 @@ const FIRST_PAGE = 1;
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
const appStore = useAppStore();
const agStore = useAccessGrantsStore();
const projectsStore = useProjectsStore();
const notify = useNotify();
const router = useRouter();
const isDeleteClicked = ref<boolean>(false);
const activeDropdown = ref<number>(-1);
const areGrantsFetching = ref<boolean>(true);
@ -272,16 +269,10 @@ function openDropdown(key: number): void {
* Holds on button click login for deleting access grant process.
*/
function onDeleteClick(grant: AccessGrant): void {
agStore.setAccessNameToDelete(grant.name);
agStore.toggleSelection(grant);
isDeleteClicked.value = true;
}
/**
* Clears access grants selection.
*/
function onClearSelection(): void {
isDeleteClicked.value = false;
agStore.clearSelection();
appStore.updateActiveModal(MODALS.deleteAccessGrant);
}
/**
@ -293,7 +284,7 @@ async function fetch(searchQuery: string): Promise<void> {
try {
await agStore.getAccessGrants(FIRST_PAGE, projectsStore.state.selectedProject.id);
} catch (error) {
await notify.error(`Unable to fetch accesses: ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
notify.error(`Unable to fetch accesses: ${error.message}`, AnalyticsErrorEventSource.ACCESS_GRANTS_PAGE);
}
}
@ -350,7 +341,7 @@ onMounted(async () => {
});
onBeforeUnmount(() => {
onClearSelection();
agStore.clearSelection();
});
</script>
<style scoped lang="scss">

View File

@ -1,258 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="confirm-delete">
<div class="confirm-delete__container">
<h1 class="confirm-delete__container__title">Delete Access</h1>
<p class="confirm-delete__container__info">
You wont be able to access bucket(s) or object(s) related to the access:
</p>
<div class="confirm-delete__container__list">
<div
v-for="accessGrant in selectedAccessGrants"
:key="accessGrant.id"
class="confirm-delete__container__list__container"
>
<div class="confirm-delete__container__list__container__item">
<p class="confirm-delete__container__list__container__item__name">
{{ accessGrant.name }}
</p>
</div>
</div>
</div>
<div>
<p>This action cannot be undone.</p>
</div>
<VInput
placeholder="Type the name of the access"
@setData="setConfirmedInput"
/>
<div class="confirm-delete__container__buttons-area">
<VButton
class="cancel-button"
label="Cancel"
width="70px"
height="44px"
:on-press="onCancelClick"
:is-white="true"
:is-disabled="isLoading"
/>
<VButton
label="Delete Access"
width="150px"
height="44px"
:on-press="onDeleteClick"
:is-disabled="isLoading || confirmedInput !== selectedAccessGrants[0].name"
:is-solid-delete="true"
has-trash-icon="true"
/>
</div>
<div class="confirm-delete__container__close-cross-container" @click="onCancelClick">
<CloseCrossIcon />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { AccessGrant } from '@/types/accessGrants';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useNotify } from '@/utils/hooks';
import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import VButton from '@/components/common/VButton.vue';
import VInput from '@/components/common/VInput.vue';
import CloseCrossIcon from '@/../static/images/common/closeCross.svg';
const FIRST_PAGE = 1;
const agStore = useAccessGrantsStore();
const projectsStore = useProjectsStore();
const notify = useNotify();
const isLoading = ref<boolean>(false);
const confirmedInput = ref<string>('');
const emit = defineEmits(['resetPagination', 'close']);
/**
* Returns list of selected access grants from store.
*/
const selectedAccessGrants = computed((): AccessGrant[] => {
return agStore.selectedAccessGrants;
});
/**
* sets Comfirmed Input property to the given value.
*/
function setConfirmedInput(value: string): void {
confirmedInput.value = value;
}
/**
* Deletes selected access grants, fetches updated list and closes popup.
*/
async function onDeleteClick(): Promise<void> {
if (isLoading.value) return;
isLoading.value = true;
try {
await agStore.deleteAccessGrants();
await notify.success(`Access Grant deleted successfully`);
} catch (error) {
await notify.error(error.message, AnalyticsErrorEventSource.CONFIRM_DELETE_AG_MODAL);
}
try {
await agStore.getAccessGrants(FIRST_PAGE, projectsStore.state.selectedProject.id);
agStore.clearSelection();
} catch (error) {
await notify.error(`Unable to fetch Access Grants. ${error.message}`, AnalyticsErrorEventSource.CONFIRM_DELETE_AG_MODAL);
}
emit('resetPagination');
isLoading.value = false;
onCancelClick();
}
/**
* Closes popup
*/
function onCancelClick(): void {
emit('close');
}
</script>
<style scoped lang="scss">
.confirm-delete {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
background: rgb(27 37 51 / 75%);
display: flex;
align-items: center;
justify-content: center;
font-family: 'font_regular', sans-serif;
font-style: normal;
&__trash-icon {
position: absolute;
left: 57%;
margin-top: -3px;
}
&__text-container {
text-align: left;
}
&__container {
border-radius: 6px;
max-width: 325px;
padding: 40px 30px;
position: relative;
display: flex;
flex-direction: column;
background-color: #fff;
&__title {
font-family: 'font_bold', sans-serif;
font-weight: bold;
font-size: 28px;
line-height: 34px;
color: #000;
}
&__info {
font-weight: normal;
font-size: 16px;
line-height: 21px;
color: #000;
margin: 25px 0 10px;
}
&__info-new {
font-weight: normal;
font-size: 16px;
line-height: 21px;
text-align: left;
color: #000;
margin: 20px 0;
}
&__list-label {
font-weight: bold;
font-size: 14px;
line-height: 18px;
color: #e30011;
font-family: 'font_medium', sans-serif;
white-space: nowrap;
margin-bottom: 30px;
}
&__list {
max-height: 255px;
overflow-y: scroll;
border-radius: 6px;
width: 100%;
&__container {
&__item {
padding: 3px 7px;
max-width: fit-content;
background: var(--c-grey-3);
border-radius: 20px;
margin-bottom: 10px;
&__name {
font-family: 'font_medium', sans-serif;
margin: 0;
font-weight: bold;
font-size: 17px;
line-height: 30px;
color: #1b2533;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
&__buttons-area {
width: fit-content;
display: flex;
align-items: center;
margin-top: 30px;
}
&__close-cross-container {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 30px;
top: 30px;
height: 24px;
width: 24px;
cursor: pointer;
&:hover .close-cross-svg-path {
fill: #2683ff;
}
}
}
}
.cancel-button {
margin-right: 15px;
}
</style>

View File

@ -0,0 +1,168 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content>
<div class="modal">
<div class="modal__header">
<DeleteIcon />
<h1 class="modal__header__title">Delete Access</h1>
</div>
<p class="modal__info">The following access will be deleted.</p>
<h3 class="modal__name">{{ name }}</h3>
<div class="modal__buttons">
<VButton
label="Cancel"
:is-white="true"
height="52px"
width="100%"
font-size="14px"
border-radius="10px"
:on-press="closeModal"
:is-disabled="isLoading"
/>
<VButton
label="Delete"
:is-solid-delete="true"
icon="trash"
width="100%"
height="52px"
font-size="14px"
border-radius="10px"
:on-press="onDelete"
:is-disabled="isLoading"
/>
</div>
</div>
</template>
</VModal>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useNotify } from '@/utils/hooks';
import { useAppStore } from '@/store/modules/appStore';
import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useLoading } from '@/composables/useLoading';
import VButton from '@/components/common/VButton.vue';
import VModal from '@/components/common/VModal.vue';
import DeleteIcon from '@/../static/images/modals/deleteAccessGrant/delete.svg';
const agStore = useAccessGrantsStore();
const projectsStore = useProjectsStore();
const appStore = useAppStore();
const notify = useNotify();
const { isLoading, withLoading } = useLoading();
/**
* Returns access name to delete from store.
*/
const name = computed((): string => {
return agStore.state.accessNameToDelete;
});
/**
* Closes delete access grant modal.
*/
function closeModal(): void {
appStore.removeActiveModal();
}
/**
* Deletes selected access grant.
*/
async function onDelete(): Promise<void> {
await withLoading(async () => {
try {
let page = agStore.state.cursor.page;
if (agStore.state.page.accessGrants.length === 1) {
page--;
if (page < 1) {
page = 1;
}
}
await agStore.deleteAccessGrants();
notify.success(`Access Grant deleted successfully`);
await agStore.getAccessGrants(page, projectsStore.state.selectedProject.id);
agStore.clearSelection();
agStore.setAccessNameToDelete('');
closeModal();
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.CONFIRM_DELETE_AG_MODAL);
}
});
}
</script>
<style scoped lang="scss">
.modal {
width: 350px;
padding: 32px;
display: flex;
flex-direction: column;
font-family: 'font_regular', sans-serif;
@media screen and (max-width: 500px) {
width: unset;
}
&__header {
display: flex;
align-items: center;
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px solid var(--c-grey-2);
&__title {
font-family: 'font_bold', sans-serif;
font-size: 24px;
line-height: 31px;
letter-spacing: -0.02em;
color: var(--c-black);
margin-left: 16px;
text-align: left;
}
}
&__info {
font-size: 14px;
line-height: 20px;
color: var(--c-black);
margin-bottom: 24px;
text-align: left;
}
&__name {
font-family: 'font_bold', sans-serif;
font-size: 14px;
line-height: 20px;
color: var(--c-black);
padding-bottom: 16px;
margin-bottom: 20px;
border-bottom: 1px solid var(--c-grey-2);
text-align: left;
}
&__buttons {
display: flex;
align-items: center;
column-gap: 16px;
@media screen and (max-width: 500px) {
column-gap: unset;
row-gap: 10px;
flex-direction: column-reverse;
}
}
}
</style>

View File

@ -31,6 +31,7 @@ class AccessGrantsState {
public edgeCredentials: EdgeCredentials = new EdgeCredentials();
public accessGrantsWebWorker: Worker | null = null;
public isAccessGrantsWebWorkerReady = false;
public accessNameToDelete = '';
}
export const useAccessGrantsStore = defineStore('accessGrants', () => {
@ -114,6 +115,10 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
state.cursor.order = order;
}
function setAccessNameToDelete(name: string): void {
state.accessNameToDelete = name;
}
function setDurationPermission(permission: DurationPermission): void {
state.permissionNotBefore = permission.notBefore;
state.permissionNotAfter = permission.notAfter;
@ -223,6 +228,7 @@ export const useAccessGrantsStore = defineStore('accessGrants', () => {
getEdgeCredentials,
setSearchQuery,
setSortingBy,
setAccessNameToDelete,
setSortingDirection,
toggleSortingDirection,
setDurationPermission,

View File

@ -30,6 +30,7 @@ import PricingPlanModal from '@/components/modals/PricingPlanModal.vue';
import NewCreateProjectModal from '@/components/modals/NewCreateProjectModal.vue';
import EditSessionTimeoutModal from '@/components/modals/EditSessionTimeoutModal.vue';
import UpgradeAccountModal from '@/components/modals/upgradeAccountFlow/UpgradeAccountModal.vue';
import DeleteAccessGrantModal from '@/components/modals/DeleteAccessGrantModal.vue';
export const APP_STATE_DROPDOWNS = {
ACCOUNT: 'isAccountDropdownShown',
@ -76,6 +77,7 @@ enum Modals {
NEW_CREATE_PROJECT = 'newCreateProject',
EDIT_SESSION_TIMEOUT = 'editSessionTimeout',
UPGRADE_ACCOUNT = 'upgradeAccount',
DELETE_ACCESS_GRANT = 'deleteAccessGrant',
}
// modals could be of VueConstructor type or Object (for composition api components).
@ -106,4 +108,5 @@ export const MODALS: Record<Modals, unknown> = {
[Modals.NEW_CREATE_PROJECT]: NewCreateProjectModal,
[Modals.EDIT_SESSION_TIMEOUT]: EditSessionTimeoutModal,
[Modals.UPGRADE_ACCOUNT]: UpgradeAccountModal,
[Modals.DELETE_ACCESS_GRANT]: DeleteAccessGrantModal,
};

View File

@ -0,0 +1,4 @@
<svg width="40" height="41" viewBox="0 0 40 41" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4423 0.980469H23.3463C28.8835 0.980469 31.0805 1.59419 33.2353 2.74664C35.3902 3.89908 37.0814 5.59024 38.2338 7.74512L38.3214 7.91102C39.4029 9.98719 39.9846 12.1804 40 17.4228V24.3268C40 29.864 39.3863 32.0609 38.2338 34.2158C37.0814 36.3707 35.3902 38.0619 33.2353 39.2143L33.0694 39.3019C30.9933 40.3834 28.8 40.965 23.5577 40.9805H16.6537C11.1165 40.9805 8.91954 40.3667 6.76466 39.2143C4.60977 38.0619 2.91861 36.3707 1.76617 34.2158L1.67858 34.0499C0.597074 31.9738 0.0154219 29.7805 0 24.5381V17.6341C0 12.097 0.613723 9.90001 1.76617 7.74512C2.91861 5.59024 4.60977 3.89908 6.76466 2.74664L6.93055 2.65905C9.00672 1.57754 11.2 0.995891 16.4423 0.980469Z" fill="#EBEEF1"/>
<path d="M27.3983 16.3472C27.7858 16.3472 28.0999 16.6614 28.0999 17.0488C28.0999 17.4268 27.8009 17.735 27.4265 17.7498L27.3983 17.7504H25.9509L24.8664 26.6069C24.7098 27.8864 23.6232 28.848 22.3342 28.848H17.7704C16.4911 28.848 15.4096 27.9005 15.2414 26.6323L14.0632 17.7504H12.6015C12.214 17.7504 11.8999 17.4363 11.8999 17.0488C11.8999 16.6708 12.1989 16.3626 12.5733 16.3478L12.6015 16.3472H27.3983ZM24.5373 17.7504H15.4787L16.6324 26.4478C16.7066 27.0073 17.1758 27.4281 17.7367 27.4444L17.7704 27.4449H22.3342C22.9029 27.4449 23.384 27.029 23.4691 26.4701L23.4737 26.4364L24.5373 17.7504ZM18.9425 20.2394L18.9471 20.2678L18.9505 20.2958L19.397 24.7615C19.4356 25.147 19.1543 25.4908 18.7687 25.5294C18.3926 25.567 18.0562 25.3001 18.0042 24.9291L18.0008 24.9011L17.5543 20.4354C17.5157 20.0499 17.797 19.7061 18.1826 19.6675C18.5491 19.6309 18.8778 19.8832 18.9425 20.2394ZM22.0723 19.6678C22.4485 19.7054 22.7254 20.0335 22.7029 20.4075L22.7006 20.4357L22.2544 24.898C22.2158 25.2835 21.872 25.5648 21.4865 25.5263C21.1103 25.4887 20.8334 25.1605 20.8559 24.7865L20.8582 24.7584L21.3044 20.2961C21.343 19.9105 21.6868 19.6292 22.0723 19.6678ZM22.296 13.3496C22.6834 13.3496 22.9975 13.6637 22.9975 14.0512C22.9975 14.4292 22.6986 14.7374 22.3242 14.7522L22.296 14.7528H17.7038C17.3164 14.7528 17.0023 14.4387 17.0023 14.0512C17.0023 13.6732 17.3012 13.365 17.6756 13.3502L17.7038 13.3496H22.296Z" fill="#56606D"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB