web/satellite: add components for new dashboard
This change just adds the components/changes needed by the all projects dashboard. Issue: https://github.com/storj/storj/issues/5514 Change-Id: Ie7926f563dd3eb47a56b234cfd004f5b69be00a8
This commit is contained in:
parent
85d6843f2e
commit
76d7bc6d18
@ -3,7 +3,32 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- if isDisabled check onPress in parent element -->
|
<!-- if isDisabled check onPress in parent element -->
|
||||||
|
<a
|
||||||
|
v-if="link"
|
||||||
|
:href="link"
|
||||||
|
:class="containerClassName"
|
||||||
|
:style="style"
|
||||||
|
tabindex="0"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
@click="onPress"
|
||||||
|
>
|
||||||
|
<slot name="icon" />
|
||||||
|
<div v-if="isWhiteGreen" class="greenCheck">✓</div>
|
||||||
|
<div v-if="isGreenWhite" class="whiteCheck">✓</div>
|
||||||
|
<span class="label" :class="{uppercase: isUppercase}">
|
||||||
|
<CopyIcon v-if="icon.toLowerCase() === 'copy'" />
|
||||||
|
<LockIcon v-if="icon.toLowerCase() === 'lock'" />
|
||||||
|
<CreditCardIcon v-if="icon.toLowerCase() === 'credit-card'" />
|
||||||
|
<DocumentIcon v-if="icon.toLowerCase() === 'document'" />
|
||||||
|
<TrashIcon v-if="icon.toLowerCase() === 'trash'" />
|
||||||
|
<FolderIcon v-if="icon.toLowerCase() === 'folder'" />
|
||||||
|
<resources-icon v-if="icon.toLowerCase() === 'resources'" />
|
||||||
|
<add-circle-icon v-if="icon.toLowerCase() === 'addcircle'" />
|
||||||
|
<span v-if="icon !== 'none'"> </span>{{ label }}</span>
|
||||||
|
</a>
|
||||||
<div
|
<div
|
||||||
|
v-else
|
||||||
:class="containerClassName"
|
:class="containerClassName"
|
||||||
:style="style"
|
:style="style"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@ -21,6 +46,8 @@
|
|||||||
<DocumentIcon v-if="icon.toLowerCase() === 'document'" />
|
<DocumentIcon v-if="icon.toLowerCase() === 'document'" />
|
||||||
<TrashIcon v-if="icon.toLowerCase() === 'trash'" />
|
<TrashIcon v-if="icon.toLowerCase() === 'trash'" />
|
||||||
<FolderIcon v-if="icon.toLowerCase() === 'folder'" />
|
<FolderIcon v-if="icon.toLowerCase() === 'folder'" />
|
||||||
|
<resources-icon v-if="icon.toLowerCase() === 'resources'" />
|
||||||
|
<add-circle-icon v-if="icon.toLowerCase() === 'addcircle'" />
|
||||||
<span v-if="icon !== 'none'"> </span>{{ label }}</span>
|
<span v-if="icon !== 'none'"> </span>{{ label }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -29,6 +56,7 @@
|
|||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import AddCircleIcon from '@/../static/images/common/addCircle.svg';
|
||||||
import CopyIcon from '@/../static/images/common/copyButtonIcon.svg';
|
import CopyIcon from '@/../static/images/common/copyButtonIcon.svg';
|
||||||
import TrashIcon from '@/../static/images/accessGrants/trashIcon.svg';
|
import TrashIcon from '@/../static/images/accessGrants/trashIcon.svg';
|
||||||
import LockIcon from '@/../static/images/common/lockIcon.svg';
|
import LockIcon from '@/../static/images/common/lockIcon.svg';
|
||||||
@ -36,8 +64,10 @@ import CreditCardIcon from '@/../static/images/common/creditCardIcon-white.svg';
|
|||||||
import DocumentIcon from '@/../static/images/common/documentIcon.svg';
|
import DocumentIcon from '@/../static/images/common/documentIcon.svg';
|
||||||
import DownloadIcon from '@/../static/images/common/download.svg';
|
import DownloadIcon from '@/../static/images/common/download.svg';
|
||||||
import FolderIcon from '@/../static/images/objects/newFolder.svg';
|
import FolderIcon from '@/../static/images/objects/newFolder.svg';
|
||||||
|
import ResourcesIcon from '@/../static/images/navigation/resources.svg';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
|
link?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
width?: string;
|
width?: string;
|
||||||
height?: string;
|
height?: string;
|
||||||
@ -57,6 +87,7 @@ const props = withDefaults(defineProps<{
|
|||||||
isUppercase?: boolean;
|
isUppercase?: boolean;
|
||||||
onPress?: () => void;
|
onPress?: () => void;
|
||||||
}>(), {
|
}>(), {
|
||||||
|
link: undefined,
|
||||||
label: 'Default',
|
label: 'Default',
|
||||||
width: 'inherit',
|
width: 'inherit',
|
||||||
height: 'inherit',
|
height: 'inherit',
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// See LICENSE for copying information.
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VModal :on-close="closeModal">
|
<VModal :on-close="() => closeModal(true)">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<EnterPassphraseIcon />
|
<EnterPassphraseIcon />
|
||||||
@ -25,7 +25,7 @@
|
|||||||
height="48px"
|
height="48px"
|
||||||
font-size="14px"
|
font-size="14px"
|
||||||
:is-transparent="true"
|
:is-transparent="true"
|
||||||
:on-press="closeModal"
|
:on-press="() => closeModal()"
|
||||||
:is-disabled="isLoading"
|
:is-disabled="isLoading"
|
||||||
/>
|
/>
|
||||||
<VButton
|
<VButton
|
||||||
@ -52,6 +52,7 @@ import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
|||||||
import { AnalyticsHttpApi } from '@/api/analytics';
|
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||||
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||||
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { RouteConfig } from '@/router';
|
||||||
|
|
||||||
import VModal from '@/components/common/VModal.vue';
|
import VModal from '@/components/common/VModal.vue';
|
||||||
import VInput from '@/components/common/VInput.vue';
|
import VInput from '@/components/common/VInput.vue';
|
||||||
@ -176,11 +177,16 @@ export default class EnterPassphraseModal extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes open bucket modal.
|
* Closes enter passphrase modal and navigates to single project dashboard from
|
||||||
|
* all projects dashboard.
|
||||||
*/
|
*/
|
||||||
public closeModal(): void {
|
public closeModal(isCloseButton = false): void {
|
||||||
if (this.isLoading) return;
|
if (this.isLoading) return;
|
||||||
|
|
||||||
|
if (!isCloseButton && this.$route.name === RouteConfig.AllProjectsDashboard.name) {
|
||||||
|
this.$router.push(RouteConfig.ProjectDashboard.path);
|
||||||
|
}
|
||||||
|
|
||||||
this.$store.commit(APP_STATE_MUTATIONS.REMOVE_ACTIVE_MODAL);
|
this.$store.commit(APP_STATE_MUTATIONS.REMOVE_ACTIVE_MODAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ Vue.use(Router);
|
|||||||
export abstract class RouteConfig {
|
export abstract class RouteConfig {
|
||||||
// root paths
|
// root paths
|
||||||
public static Root = new NavigationLink('/', 'Root');
|
public static Root = new NavigationLink('/', 'Root');
|
||||||
|
public static AllProjectsDashboard = new NavigationLink('/all', 'All Projects');
|
||||||
public static Login = new NavigationLink('/login', 'Login');
|
public static Login = new NavigationLink('/login', 'Login');
|
||||||
public static Register = new NavigationLink('/signup', 'Register');
|
public static Register = new NavigationLink('/signup', 'Register');
|
||||||
public static RegisterSuccess = new NavigationLink('/signup-success', 'RegisterSuccess');
|
public static RegisterSuccess = new NavigationLink('/signup-success', 'RegisterSuccess');
|
||||||
|
@ -30,6 +30,7 @@ import EnterPassphraseModal from '@/components/modals/EnterPassphraseModal.vue';
|
|||||||
|
|
||||||
export const APP_STATE_DROPDOWNS = {
|
export const APP_STATE_DROPDOWNS = {
|
||||||
ACCOUNT: 'isAccountDropdownShown',
|
ACCOUNT: 'isAccountDropdownShown',
|
||||||
|
ALL_DASH_ACCOUNT: 'allProjectsDashboardAccount',
|
||||||
SELECT_PROJECT: 'isSelectProjectDropdownShown',
|
SELECT_PROJECT: 'isSelectProjectDropdownShown',
|
||||||
RESOURCES: 'isResourcesDropdownShown',
|
RESOURCES: 'isResourcesDropdownShown',
|
||||||
QUICK_START: 'isQuickStartDropdownShown',
|
QUICK_START: 'isQuickStartDropdownShown',
|
||||||
|
464
web/satellite/src/views/all-dashboard/components/Heading.vue
Normal file
464
web/satellite/src/views/all-dashboard/components/Heading.vue
Normal file
@ -0,0 +1,464 @@
|
|||||||
|
// Copyright (C) 2023 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<div class="header__content">
|
||||||
|
<LogoIcon class="header__content__logo" />
|
||||||
|
<div class="header__content__actions">
|
||||||
|
<VButton
|
||||||
|
class="header__content__actions__docs"
|
||||||
|
icon="resources"
|
||||||
|
is-white
|
||||||
|
:link="link"
|
||||||
|
:on-press="sendDocsEvent"
|
||||||
|
label="Go to Docs"
|
||||||
|
/>
|
||||||
|
<my-account-button />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="header__mobile-area">
|
||||||
|
<div class="header__mobile-area__container">
|
||||||
|
<header class="header__mobile-area__container__header">
|
||||||
|
<div class="header__mobile-area__container__header__logo" @click.stop="goToProjects">
|
||||||
|
<LogoIcon />
|
||||||
|
</div>
|
||||||
|
<CrossIcon v-if="isNavOpened" @click="toggleNavigation" />
|
||||||
|
<MenuIcon v-else @click="toggleNavigation" />
|
||||||
|
</header>
|
||||||
|
<div v-if="isNavOpened" class="header__mobile-area__container__wrap">
|
||||||
|
<a
|
||||||
|
aria-label="Docs"
|
||||||
|
class="header__mobile-area__container__wrap__item-container"
|
||||||
|
:href="link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
@click="sendDocsEvent"
|
||||||
|
>
|
||||||
|
<div class="header__mobile-area__container__wrap__item-container__left">
|
||||||
|
<resources-icon class="header__mobile-area__container__wrap__item-container__left__image" />
|
||||||
|
<p class="header__mobile-area__container__wrap__item-container__left__label">Go to Docs</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="header__mobile-area__container__wrap__border" />
|
||||||
|
<div class="account-area">
|
||||||
|
<div class="account-area__wrap" aria-roledescription="account-area" @click.stop="toggleAccountDropdown">
|
||||||
|
<div class="account-area__wrap__left">
|
||||||
|
<AccountIcon class="account-area__wrap__left__icon" />
|
||||||
|
<p class="account-area__wrap__left__label">My Account</p>
|
||||||
|
<p class="account-area__wrap__left__label-small">Account</p>
|
||||||
|
<TierBadgePro v-if="user.paidTier" class="account-area__wrap__left__tier-badge" />
|
||||||
|
<TierBadgeFree v-else class="account-area__wrap__left__tier-badge" />
|
||||||
|
</div>
|
||||||
|
<ArrowIcon class="account-area__wrap__arrow" />
|
||||||
|
</div>
|
||||||
|
<div v-if="isAccountDropdownShown" class="account-area__dropdown">
|
||||||
|
<div class="account-area__dropdown__header">
|
||||||
|
<div class="account-area__dropdown__header__left">
|
||||||
|
<SatelliteIcon />
|
||||||
|
<h2 class="account-area__dropdown__header__left__label">Satellite</h2>
|
||||||
|
</div>
|
||||||
|
<div class="account-area__dropdown__header__right">
|
||||||
|
<p class="account-area__dropdown__header__right__sat">{{ satellite }}</p>
|
||||||
|
<a
|
||||||
|
class="account-area__dropdown__header__right__link"
|
||||||
|
href="https://docs.storj.io/dcs/concepts/satellite"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<InfoIcon />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="account-area__dropdown__item" @click="navigateToBilling">
|
||||||
|
<BillingIcon />
|
||||||
|
<p class="account-area__dropdown__item__label">Billing</p>
|
||||||
|
</div>
|
||||||
|
<div class="account-area__dropdown__item" @click="navigateToSettings">
|
||||||
|
<SettingsIcon />
|
||||||
|
<p class="account-area__dropdown__item__label">Account Settings</p>
|
||||||
|
</div>
|
||||||
|
<div class="account-area__dropdown__item" @click="onLogout">
|
||||||
|
<LogoutIcon />
|
||||||
|
<p class="account-area__dropdown__item__label">Logout</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useNotify, useRoute, useRouter, useStore } from '@/utils/hooks';
|
||||||
|
import MyAccountButton from '@/views/all-dashboard/components/MyAccountButton.vue';
|
||||||
|
import {
|
||||||
|
AnalyticsErrorEventSource,
|
||||||
|
AnalyticsEvent,
|
||||||
|
} from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||||
|
import { RouteConfig } from '@/router';
|
||||||
|
import { User } from '@/types/users';
|
||||||
|
import {
|
||||||
|
APP_STATE_ACTIONS,
|
||||||
|
NOTIFICATION_ACTIONS,
|
||||||
|
PM_ACTIONS,
|
||||||
|
} from '@/utils/constants/actionNames';
|
||||||
|
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||||
|
import { USER_ACTIONS } from '@/store/modules/users';
|
||||||
|
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||||
|
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||||
|
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||||
|
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||||
|
import { AB_TESTING_ACTIONS } from '@/store/modules/abTesting';
|
||||||
|
import { AuthHttpApi } from '@/api/auth';
|
||||||
|
import { MetaUtils } from '@/utils/meta';
|
||||||
|
|
||||||
|
import VButton from '@/components/common/VButton.vue';
|
||||||
|
|
||||||
|
import LogoIcon from '@/../static/images/logo.svg';
|
||||||
|
import ResourcesIcon from '@/../static/images/navigation/resources.svg';
|
||||||
|
import BillingIcon from '@/../static/images/navigation/billing.svg';
|
||||||
|
import LogoutIcon from '@/../static/images/navigation/logout.svg';
|
||||||
|
import SettingsIcon from '@/../static/images/navigation/settings.svg';
|
||||||
|
import TierBadgeFree from '@/../static/images/navigation/tierBadgeFree.svg';
|
||||||
|
import TierBadgePro from '@/../static/images/navigation/tierBadgePro.svg';
|
||||||
|
import InfoIcon from '@/../static/images/navigation/info.svg';
|
||||||
|
import SatelliteIcon from '@/../static/images/navigation/satellite.svg';
|
||||||
|
import AccountIcon from '@/../static/images/navigation/account.svg';
|
||||||
|
import ArrowIcon from '@/../static/images/navigation/arrowExpandRight.svg';
|
||||||
|
import CrossIcon from '@/../static/images/common/closeCross.svg';
|
||||||
|
import MenuIcon from '@/../static/images/navigation/menu.svg';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const store = useStore();
|
||||||
|
const analytics = new AnalyticsHttpApi();
|
||||||
|
const auth = new AuthHttpApi();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
|
const link = 'https://docs.storj.io/';
|
||||||
|
|
||||||
|
const isAccountDropdownShown = ref(false);
|
||||||
|
const isNavOpened = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns satellite name from store.
|
||||||
|
*/
|
||||||
|
const satellite = computed((): string => {
|
||||||
|
return store.state.appStateModule.satelliteName;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns user from store.
|
||||||
|
*/
|
||||||
|
const user = computed((): User => {
|
||||||
|
return store.getters.user;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if new billing screen should be used.
|
||||||
|
*/
|
||||||
|
const isNewBillingScreen = computed((): boolean => {
|
||||||
|
return MetaUtils.getMetaContent('new-billing-screen') === 'true';
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles account dropdown visibility.
|
||||||
|
*/
|
||||||
|
function toggleAccountDropdown(): void {
|
||||||
|
isAccountDropdownShown.value = !isAccountDropdownShown.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles navigation content visibility.
|
||||||
|
*/
|
||||||
|
function toggleNavigation(): void {
|
||||||
|
isNavOpened.value = !isNavOpened.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends "View Docs" event to segment and opens link.
|
||||||
|
*/
|
||||||
|
function goToProjects(): void {
|
||||||
|
analytics.pageVisit(RouteConfig.AllProjectsDashboard.path);
|
||||||
|
router.push(RouteConfig.AllProjectsDashboard.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToBilling(): void {
|
||||||
|
toggleNavigation();
|
||||||
|
|
||||||
|
const routeConf = isNewBillingScreen ?
|
||||||
|
RouteConfig.AccountSettings.with(RouteConfig.Billing).with(RouteConfig.BillingOverview).path :
|
||||||
|
RouteConfig.AccountSettings.with(RouteConfig.Billing).path;
|
||||||
|
router.push(routeConf);
|
||||||
|
analytics.pageVisit(routeConf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates user to account settings page.
|
||||||
|
*/
|
||||||
|
function navigateToSettings(): void {
|
||||||
|
toggleNavigation();
|
||||||
|
analytics.pageVisit(RouteConfig.AccountSettings.with(RouteConfig.Settings).path);
|
||||||
|
router.push(RouteConfig.AccountSettings.with(RouteConfig.Settings).path).catch(() => {return;});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logouts user and navigates to login page.
|
||||||
|
*/
|
||||||
|
async function onLogout(): Promise<void> {
|
||||||
|
analytics.pageVisit(RouteConfig.Login.path);
|
||||||
|
await router.push(RouteConfig.Login.path);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
store.dispatch(PM_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(PROJECTS_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(USER_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(ACCESS_GRANTS_ACTIONS.STOP_ACCESS_GRANTS_WEB_WORKER),
|
||||||
|
store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(NOTIFICATION_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(BUCKET_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(OBJECTS_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(APP_STATE_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(PAYMENTS_ACTIONS.CLEAR_PAYMENT_INFO),
|
||||||
|
store.dispatch(AB_TESTING_ACTIONS.RESET),
|
||||||
|
store.dispatch('files/clear'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
analytics.eventTriggered(AnalyticsEvent.LOGOUT_CLICKED);
|
||||||
|
await auth.logout();
|
||||||
|
} catch (error) {
|
||||||
|
await notify.error(error.message, AnalyticsErrorEventSource.NAVIGATION_ACCOUNT_AREA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends "View Docs" event to segment and opens link.
|
||||||
|
*/
|
||||||
|
function sendDocsEvent(): void {
|
||||||
|
analytics.pageVisit(link);
|
||||||
|
analytics.eventTriggered(AnalyticsEvent.VIEW_DOCS_CLICKED);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.header {
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__logo {
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 37px;
|
||||||
|
width: 207px;
|
||||||
|
height: 37px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
&__docs {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__mobile-area {
|
||||||
|
background-color: #fff;
|
||||||
|
font-family: 'font_regular', sans-serif;
|
||||||
|
box-shadow: 0 0 32px rgb(0 0 0 / 4%);
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 32px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 4rem;
|
||||||
|
|
||||||
|
&__logo {
|
||||||
|
width: 211px;
|
||||||
|
max-width: 211px;
|
||||||
|
height: 37px;
|
||||||
|
max-height: 37px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 211px;
|
||||||
|
height: 37px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__wrap {
|
||||||
|
position: fixed;
|
||||||
|
top: 4rem;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
&__item-container {
|
||||||
|
padding: 14px 32px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-left: 4px solid #fff;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
position: static;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 48px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-left: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__border {
|
||||||
|
margin: 0 32px 16px;
|
||||||
|
border: 0.5px solid var(--c-grey-2);
|
||||||
|
width: calc(100% - 48px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__mobile-area {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-area {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&__wrap {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 16px 32px;
|
||||||
|
height: 48px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
position: static;
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&__label,
|
||||||
|
&__label-small {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
margin: 0 6px 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label-small {
|
||||||
|
display: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dropdown {
|
||||||
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
background: var(--c-grey-1);
|
||||||
|
padding: 16px 32px;
|
||||||
|
border: 1px solid var(--c-grey-2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&__left,
|
||||||
|
&__right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sat {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
max-height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 32px;
|
||||||
|
background: var(--c-grey-1);
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
margin-left: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,343 @@
|
|||||||
|
// Copyright (C) 2023 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="account-button"
|
||||||
|
@click.stop.prevent="openDropdown"
|
||||||
|
@mouseenter="isHoveredOver = true"
|
||||||
|
@mouseleave="isHoveredOver = false"
|
||||||
|
>
|
||||||
|
<div class="account-button__button">
|
||||||
|
<account-icon class="account-button__button__icon" :class="{active: isHoveredOver || isDropdownOpen}" />
|
||||||
|
<span class="account-button__button__label" :class="{active: isHoveredOver || isDropdownOpen}">My Account</span>
|
||||||
|
<arrow-down-icon class="account-button__arrow" :class="{active: isDropdownOpen, hovered: isHoveredOver}" />
|
||||||
|
</div>
|
||||||
|
<div v-if="isDropdownOpen" v-click-outside="closeDropdown" class="account-button__dropdown">
|
||||||
|
<div class="account-button__dropdown__header">
|
||||||
|
<div class="account-button__dropdown__header__left">
|
||||||
|
<SatelliteIcon />
|
||||||
|
<h2 class="account-button__dropdown__header__left__label">Account Region</h2>
|
||||||
|
</div>
|
||||||
|
<div class="account-button__dropdown__header__right">
|
||||||
|
<p class="account-button__dropdown__header__right__sat">{{ satellite }}</p>
|
||||||
|
<a
|
||||||
|
href="https://docs.storj.io/dcs/concepts/satellite"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="account-button__dropdown__header__right__link"
|
||||||
|
@click.stop="closeDropdown"
|
||||||
|
>
|
||||||
|
<InfoIcon />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div tabindex="0" class="account-button__dropdown__item" @click.stop="navigateToBilling" @keyup.enter="navigateToBilling">
|
||||||
|
<BillingIcon />
|
||||||
|
<p class="account-button__dropdown__item__label">Billing</p>
|
||||||
|
</div>
|
||||||
|
<div tabindex="0" class="account-button__dropdown__item" @click.stop="navigateToSettings" @keyup.enter="navigateToSettings">
|
||||||
|
<SettingsIcon />
|
||||||
|
<p class="account-button__dropdown__item__label">Account Settings</p>
|
||||||
|
</div>
|
||||||
|
<div tabindex="0" class="account-button__dropdown__item" @click.stop="onLogout" @keyup.enter="onLogout">
|
||||||
|
<LogoutIcon />
|
||||||
|
<p class="account-button__dropdown__item__label">Logout</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { RouteConfig } from '@/router';
|
||||||
|
import { useNotify, useRoute, useRouter, useStore } from '@/utils/hooks';
|
||||||
|
import {
|
||||||
|
APP_STATE_ACTIONS,
|
||||||
|
NOTIFICATION_ACTIONS,
|
||||||
|
PM_ACTIONS,
|
||||||
|
} from '@/utils/constants/actionNames';
|
||||||
|
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||||
|
import { USER_ACTIONS } from '@/store/modules/users';
|
||||||
|
import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
|
||||||
|
import { BUCKET_ACTIONS } from '@/store/modules/buckets';
|
||||||
|
import { OBJECTS_ACTIONS } from '@/store/modules/objects';
|
||||||
|
import { PAYMENTS_ACTIONS } from '@/store/modules/payments';
|
||||||
|
import { AB_TESTING_ACTIONS } from '@/store/modules/abTesting';
|
||||||
|
import {
|
||||||
|
AnalyticsErrorEventSource,
|
||||||
|
AnalyticsEvent,
|
||||||
|
} from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { MetaUtils } from '@/utils/meta';
|
||||||
|
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||||
|
import { AuthHttpApi } from '@/api/auth';
|
||||||
|
import { APP_STATE_DROPDOWNS } from '@/utils/constants/appStatePopUps';
|
||||||
|
|
||||||
|
import AccountIcon from '@/../static/images/navigation/account.svg';
|
||||||
|
import ArrowDownIcon from '@/../static/images/common/dropIcon.svg';
|
||||||
|
import LogoutIcon from '@/../static/images/navigation/logout.svg';
|
||||||
|
import SatelliteIcon from '@/../static/images/navigation/satellite.svg';
|
||||||
|
import InfoIcon from '@/../static/images/navigation/info.svg';
|
||||||
|
import BillingIcon from '@/../static/images/navigation/billing.svg';
|
||||||
|
import SettingsIcon from '@/../static/images/navigation/settings.svg';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const store = useStore();
|
||||||
|
const notify = useNotify();
|
||||||
|
const analytics = new AnalyticsHttpApi();
|
||||||
|
const auth = new AuthHttpApi();
|
||||||
|
|
||||||
|
const isHoveredOver = ref(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if account dropdown is open.
|
||||||
|
*/
|
||||||
|
const isDropdownOpen = computed((): boolean => {
|
||||||
|
return store.state.appStateModule.appState.activeDropdown === APP_STATE_DROPDOWNS.ALL_DASH_ACCOUNT;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if new billing screen should be used.
|
||||||
|
*/
|
||||||
|
const isNewBillingScreen = computed((): boolean => {
|
||||||
|
return MetaUtils.getMetaContent('new-billing-screen') === 'true';
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns satellite name from store.
|
||||||
|
*/
|
||||||
|
const satellite = computed((): string => {
|
||||||
|
return store.state.appStateModule.satelliteName;
|
||||||
|
});
|
||||||
|
|
||||||
|
function openDropdown(): void {
|
||||||
|
store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACTIVE_DROPDOWN, APP_STATE_DROPDOWNS.ALL_DASH_ACCOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDropdown(): void {
|
||||||
|
store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates user to billing page.
|
||||||
|
*/
|
||||||
|
function navigateToBilling(): void {
|
||||||
|
closeDropdown();
|
||||||
|
|
||||||
|
const routeConf = isNewBillingScreen ?
|
||||||
|
RouteConfig.Account.with(RouteConfig.Billing).with(RouteConfig.BillingOverview).path :
|
||||||
|
RouteConfig.Account.with(RouteConfig.Billing).path;
|
||||||
|
router.push(routeConf);
|
||||||
|
analytics.pageVisit(routeConf);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates user to account settings page.
|
||||||
|
*/
|
||||||
|
function navigateToSettings(): void {
|
||||||
|
closeDropdown();
|
||||||
|
analytics.pageVisit(RouteConfig.Account.with(RouteConfig.Settings).path);
|
||||||
|
router.push(RouteConfig.Account.with(RouteConfig.Settings).path).catch(() => {return;});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logouts user and navigates to login page.
|
||||||
|
*/
|
||||||
|
async function onLogout(): Promise<void> {
|
||||||
|
analytics.pageVisit(RouteConfig.Login.path);
|
||||||
|
await router.push(RouteConfig.Login.path);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
store.dispatch(PM_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(PROJECTS_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(USER_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(ACCESS_GRANTS_ACTIONS.STOP_ACCESS_GRANTS_WEB_WORKER),
|
||||||
|
store.dispatch(ACCESS_GRANTS_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(NOTIFICATION_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(BUCKET_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(OBJECTS_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(APP_STATE_ACTIONS.CLEAR),
|
||||||
|
store.dispatch(PAYMENTS_ACTIONS.CLEAR_PAYMENT_INFO),
|
||||||
|
store.dispatch(AB_TESTING_ACTIONS.RESET),
|
||||||
|
store.dispatch('files/clear'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
analytics.eventTriggered(AnalyticsEvent.LOGOUT_CLICKED);
|
||||||
|
await auth.logout();
|
||||||
|
} catch (error) {
|
||||||
|
await notify.error(error.message, AnalyticsErrorEventSource.NAVIGATION_ACCOUNT_AREA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.account-button {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 14px 18px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--c-white);
|
||||||
|
border: 1px solid var(--c-grey-3);
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 44px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
border: 1px solid var(--c-blue-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
transition-duration: 0.5s;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-family: 'font_medium', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 23px;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
margin-right: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label.active {
|
||||||
|
color: var(--c-blue-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon.active {
|
||||||
|
|
||||||
|
:deep(path) {
|
||||||
|
fill: var(--c-blue-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
right: 0;
|
||||||
|
background: var(--c-white);
|
||||||
|
font-family: 'font_regular', sans-serif;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
width: 270px;
|
||||||
|
z-index: 999;
|
||||||
|
cursor: default;
|
||||||
|
border: 1px solid var(--c-grey-2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: 0 -2px 16px rgb(0 0 0 / 10%);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
background: var(--c-grey-1);
|
||||||
|
padding: 16px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
border: 1px solid var(--c-grey-2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
|
||||||
|
&__left,
|
||||||
|
&__right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__sat {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
max-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link:focus {
|
||||||
|
|
||||||
|
svg :deep(path) {
|
||||||
|
fill: var(--c-blue-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1px solid var(--c-grey-2);
|
||||||
|
padding: 16px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
margin-left: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--c-grey-1);
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--c-blue-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(path) {
|
||||||
|
fill: var(--c-blue-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--c-grey-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__arrow {
|
||||||
|
transition-duration: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__arrow.active {
|
||||||
|
transform: rotate(180deg) scaleX(-1);
|
||||||
|
|
||||||
|
:deep(path) {
|
||||||
|
fill: var(--c-blue-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__arrow.hovered {
|
||||||
|
|
||||||
|
:deep(path) {
|
||||||
|
fill: var(--c-blue-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
125
web/satellite/src/views/all-dashboard/components/MyProjects.vue
Normal file
125
web/satellite/src/views/all-dashboard/components/MyProjects.vue
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// Copyright (C) 2023 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="my-projects">
|
||||||
|
<div class="my-projects__header">
|
||||||
|
<span class="my-projects__header__title">My Projects</span>
|
||||||
|
|
||||||
|
<VButton
|
||||||
|
class="my-projects__header__button"
|
||||||
|
icon="addcircle"
|
||||||
|
is-white
|
||||||
|
:on-press="onCreateProjectClicked"
|
||||||
|
label="Create a Project"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-projects__list">
|
||||||
|
<project-item v-for="project in projects" :key="project.id" :project="project" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { Project } from '@/types/projects';
|
||||||
|
import { useRoute, useRouter, useStore } from '@/utils/hooks';
|
||||||
|
import { RouteConfig } from '@/router';
|
||||||
|
import {
|
||||||
|
AnalyticsEvent,
|
||||||
|
} from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { User } from '@/types/users';
|
||||||
|
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||||
|
import { MODALS } from '@/utils/constants/appStatePopUps';
|
||||||
|
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||||
|
import ProjectItem from '@/views/all-dashboard/components/ProjectItem.vue';
|
||||||
|
|
||||||
|
import VButton from '@/components/common/VButton.vue';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const store = useStore();
|
||||||
|
const analytics = new AnalyticsHttpApi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns projects list from store.
|
||||||
|
*/
|
||||||
|
const projects = computed((): Project[] => {
|
||||||
|
return store.getters.projects;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route to create project page.
|
||||||
|
*/
|
||||||
|
function onCreateProjectClicked(): void {
|
||||||
|
analytics.eventTriggered(AnalyticsEvent.CREATE_NEW_CLICKED);
|
||||||
|
|
||||||
|
const user: User = store.getters.user;
|
||||||
|
const ownProjectsCount: number = store.getters.projectsCount;
|
||||||
|
|
||||||
|
if (!user.paidTier && user.projectLimit === ownProjectsCount) {
|
||||||
|
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.createProjectPrompt);
|
||||||
|
} else {
|
||||||
|
analytics.pageVisit(RouteConfig.CreateProject.path);
|
||||||
|
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.createProject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.my-projects {
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
@media screen and (max-width: 425px) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: start;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 31px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
|
||||||
|
& :deep(.project-item) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1024px) {
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 786px) {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 425px) {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
274
web/satellite/src/views/all-dashboard/components/ProjectItem.vue
Normal file
274
web/satellite/src/views/all-dashboard/components/ProjectItem.vue
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
// Copyright (C) 2023 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="project.id" class="project-item">
|
||||||
|
<div class="project-item__header">
|
||||||
|
<div class="project-item__header__tag" :class="{member: !isOwner}">
|
||||||
|
<box-icon />
|
||||||
|
|
||||||
|
<span> {{ isOwner ? 'Owner': 'Member' }} </span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
v-click-outside="closeDropDown" href="" class="project-item__header__menu"
|
||||||
|
:class="{open: isDropdownOpen}" @click.stop.prevent="toggleDropDown"
|
||||||
|
>
|
||||||
|
<menu-icon />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div v-if="isDropdownOpen" class="project-item__header__dropdown">
|
||||||
|
<div class="project-item__header__dropdown__item" @click.stop.prevent="goToProjectEdit">
|
||||||
|
<gear-icon />
|
||||||
|
<p class="project-item__header__dropdown__item__label">Project settings</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-item__header__dropdown__item" @click.stop.prevent="goToProjectMembers">
|
||||||
|
<users-icon />
|
||||||
|
<p class="project-item__header__dropdown__item__label">Invite members</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="project-item__name">
|
||||||
|
{{ project.name }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="project-item__description">
|
||||||
|
{{ project.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<VButton
|
||||||
|
class="project-item__button"
|
||||||
|
width="unset"
|
||||||
|
height="unset"
|
||||||
|
:on-press="onOpenClicked"
|
||||||
|
label="Open Project"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { Project } from '@/types/projects';
|
||||||
|
import { useNotify, useRoute, useRouter, useStore } from '@/utils/hooks';
|
||||||
|
import {
|
||||||
|
AnalyticsEvent,
|
||||||
|
} from '@/utils/constants/analyticsEventNames';
|
||||||
|
import { User } from '@/types/users';
|
||||||
|
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||||
|
import { PROJECTS_ACTIONS } from '@/store/modules/projects';
|
||||||
|
import { LocalData } from '@/utils/localData';
|
||||||
|
import { APP_STATE_ACTIONS, PM_ACTIONS } from '@/utils/constants/actionNames';
|
||||||
|
import { OBJECTS_MUTATIONS } from '@/store/modules/objects';
|
||||||
|
import { RouteConfig } from '@/router';
|
||||||
|
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
|
||||||
|
import { MODALS } from '@/utils/constants/appStatePopUps';
|
||||||
|
|
||||||
|
import VButton from '@/components/common/VButton.vue';
|
||||||
|
|
||||||
|
import GearIcon from '@/../static/images/common/gearIcon.svg';
|
||||||
|
import UsersIcon from '@/../static/images/navigation/users.svg';
|
||||||
|
import MenuIcon from '@/../static/images/allDashboard/menu.svg';
|
||||||
|
import BoxIcon from '@/../static/images/allDashboard/box.svg';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
const store = useStore();
|
||||||
|
const analytics = new AnalyticsHttpApi();
|
||||||
|
const notify = useNotify();
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
project?: Project,
|
||||||
|
}>(), {
|
||||||
|
project: () => new Project(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* isDropdownOpen if dropdown is open.
|
||||||
|
*/
|
||||||
|
const isDropdownOpen = computed((): boolean => {
|
||||||
|
return store.state.appStateModule.appState.activeDropdown === props.project.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns user entity from store.
|
||||||
|
*/
|
||||||
|
const user = computed((): User => {
|
||||||
|
return store.getters.user;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns projects list from store.
|
||||||
|
*/
|
||||||
|
const isOwner = computed((): boolean => {
|
||||||
|
return props.project.ownerId === user.value.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleDropDown() {
|
||||||
|
store.dispatch(APP_STATE_ACTIONS.TOGGLE_ACTIVE_DROPDOWN, props.project.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDropDown() {
|
||||||
|
store.dispatch(APP_STATE_ACTIONS.CLOSE_POPUPS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all project related information.
|
||||||
|
*/
|
||||||
|
async function onOpenClicked(): Promise<void> {
|
||||||
|
await selectProject();
|
||||||
|
await analytics.eventTriggered(AnalyticsEvent.NAVIGATE_PROJECTS);
|
||||||
|
store.commit(APP_STATE_MUTATIONS.UPDATE_ACTIVE_MODAL, MODALS.enterPassphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function selectProject() {
|
||||||
|
await store.dispatch(PROJECTS_ACTIONS.SELECT, props.project.id);
|
||||||
|
LocalData.setSelectedProjectId(props.project.id);
|
||||||
|
await store.dispatch(PM_ACTIONS.SET_SEARCH_QUERY, '');
|
||||||
|
|
||||||
|
store.commit(OBJECTS_MUTATIONS.CLEAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to project members page.
|
||||||
|
*/
|
||||||
|
async function goToProjectMembers(): Promise<void> {
|
||||||
|
await selectProject();
|
||||||
|
analytics.pageVisit(RouteConfig.Users.path);
|
||||||
|
router.push(RouteConfig.Users.path);
|
||||||
|
closeDropDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all project related information and goes to edit project page.
|
||||||
|
*/
|
||||||
|
async function goToProjectEdit(): Promise<void> {
|
||||||
|
await selectProject();
|
||||||
|
analytics.pageVisit(RouteConfig.EditProjectDetails.path);
|
||||||
|
router.push(RouteConfig.EditProjectDetails.path);
|
||||||
|
closeDropDown();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.project-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 24px;
|
||||||
|
height: 200px;
|
||||||
|
background: var(--c-white);
|
||||||
|
box-shadow: 0 0 20px rgb(0 0 0 / 4%);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&__tag {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: 1px solid var(--c-purple-2);
|
||||||
|
border-radius: 24px;
|
||||||
|
color: var(--c-purple-4);
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: 'font_regular', sans-serif;
|
||||||
|
|
||||||
|
&.member {
|
||||||
|
color: var(--c-yellow-5);
|
||||||
|
border-color: var(--c-yellow-1);
|
||||||
|
|
||||||
|
:deep(path) {
|
||||||
|
fill: var(--c-yellow-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__menu {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
align-content: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.open {
|
||||||
|
background: var(--c-grey-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
right: 0;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 7px 20px rgb(0 0 0 / 15%);
|
||||||
|
border: 1px solid var(--c-grey-2);
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 25px;
|
||||||
|
font-family: 'font_regular', sans-serif;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
margin: 0 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--c-grey-1);
|
||||||
|
font-family: 'font_medium', sans-serif;
|
||||||
|
color: var(--c-blue-3);
|
||||||
|
|
||||||
|
svg :deep(path) {
|
||||||
|
fill: var(--c-blue-3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'font_regular', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 31px;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
font-family: 'font_regular', sans-serif;
|
||||||
|
color: var(--c-grey-6);
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
3
web/satellite/static/images/allDashboard/box.svg
Normal file
3
web/satellite/static/images/allDashboard/box.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="10" height="12" viewBox="0 0 10 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.13404 0.652105L5.15302 0.662491L9.44915 3.14357C9.59279 3.22653 9.66993 3.36706 9.68057 3.51186L9.68147 3.52864L9.68184 3.54736V8.46475C9.68184 8.62442 9.60006 8.77242 9.46612 8.85756L9.44836 8.86831L5.1522 11.3382C5.01422 11.4175 4.84577 11.4206 4.70545 11.3476L4.68649 11.3372L0.43205 8.86732C0.294547 8.7875 0.20772 8.64332 0.200723 8.48542L0.200195 8.46475L0.20035 3.53861L0.200195 3.52439C0.20104 3.37069 0.278044 3.22133 0.415856 3.13378L0.432684 3.12357L4.68716 0.66265C4.81842 0.586724 4.97752 0.579914 5.11378 0.642238L5.13404 0.652105ZM8.75081 4.35305L5.38566 6.29643V10.13L8.75081 8.19537V4.35305ZM1.13125 4.35694V8.19673L4.45468 10.1261V6.29507L1.13125 4.35694ZM4.92034 1.60319L1.57515 3.5381L4.92135 5.48952L8.28549 3.54665L4.92034 1.60319Z" fill="#7B61FF"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 887 B |
3
web/satellite/static/images/allDashboard/menu.svg
Normal file
3
web/satellite/static/images/allDashboard/menu.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="4" viewBox="0 0 16 4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8 0C6.9 0 6 0.9 6 2C6 3.1 6.9 4 8 4C9.1 4 10 3.1 10 2C10 0.9 9.1 0 8 0ZM14 0C12.9 0 12 0.9 12 2C12 3.1 12.9 4 14 4C15.1 4 16 3.1 16 2C16 0.9 15.1 0 14 0ZM2 0C0.9 0 0 0.9 0 2C0 3.1 0.9 4 2 4C3.1 4 4 3.1 4 2C4 0.9 3.1 0 2 0Z" fill="#7C8794"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 352 B |
3
web/satellite/static/images/common/addCircle.svg
Normal file
3
web/satellite/static/images/common/addCircle.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.00001 0.700195C10.4794 0.700195 13.3 3.5208 13.3 7.0002C13.3 10.4796 10.4794 13.3002 7.00001 13.3002C3.52062 13.3002 0.700012 10.4796 0.700012 7.0002C0.700012 3.5208 3.52062 0.700195 7.00001 0.700195ZM7.00001 1.8552C4.15851 1.8552 1.85501 4.15869 1.85501 7.0002C1.85501 9.8417 4.15851 12.1452 7.00001 12.1452C9.84152 12.1452 12.145 9.8417 12.145 7.0002C12.145 4.15869 9.84152 1.8552 7.00001 1.8552ZM7.5247 4.82943L7.52494 4.83872L7.52491 6.4563H9.09486C9.41705 6.4563 9.68118 6.71179 9.6919 7.0338C9.70216 7.3423 9.46039 7.60072 9.15188 7.61099L9.14259 7.61122L7.52491 7.6113L7.52501 9.2199C7.52501 9.53884 7.26646 9.7974 6.94751 9.7974C6.63635 9.7974 6.38266 9.5513 6.37047 9.24312L6.37001 9.2199L6.36991 7.6113H4.76141C4.44247 7.6113 4.18391 7.35274 4.18391 7.0338C4.18391 6.72263 4.43001 6.46894 4.73819 6.45675L4.76141 6.4563H6.36991L6.37001 4.88645C6.37001 4.56426 6.62551 4.30012 6.94751 4.28941C7.25602 4.27915 7.51444 4.52092 7.5247 4.82943Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in New Issue
Block a user