satellite/{console, web}: remove AllProjectDashboard feature flag

Removed AllProjectDashboard feature flag.
Removed unused Vue components.
Fixed wrong redirect on reload if pricing packages are disabled.
Fixed wrong redirect on reload if billing features are enabled.

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

Change-Id: I9081a6f737c45fb48da5b23c016a42e23021c4ce
This commit is contained in:
Vitalii 2023-10-24 15:50:27 +03:00 committed by Storj Robot
parent f319af5a35
commit 4ba2703783
22 changed files with 30 additions and 582 deletions

View File

@ -32,7 +32,6 @@ type FrontendConfig struct {
PublicLinksharingURL string `json:"publicLinksharingURL"` PublicLinksharingURL string `json:"publicLinksharingURL"`
PathwayOverviewEnabled bool `json:"pathwayOverviewEnabled"` PathwayOverviewEnabled bool `json:"pathwayOverviewEnabled"`
Captcha console.CaptchaConfig `json:"captcha"` Captcha console.CaptchaConfig `json:"captcha"`
AllProjectsDashboard bool `json:"allProjectsDashboard"`
LimitsAreaEnabled bool `json:"limitsAreaEnabled"` LimitsAreaEnabled bool `json:"limitsAreaEnabled"`
DefaultPaidStorageLimit memory.Size `json:"defaultPaidStorageLimit"` DefaultPaidStorageLimit memory.Size `json:"defaultPaidStorageLimit"`
DefaultPaidBandwidthLimit memory.Size `json:"defaultPaidBandwidthLimit"` DefaultPaidBandwidthLimit memory.Size `json:"defaultPaidBandwidthLimit"`

View File

@ -99,7 +99,6 @@ type Config struct {
LinksharingURL string `help:"url link for linksharing requests within the application" default:"https://link.storjsatelliteshare.io" devDefault:"http://localhost:8001"` LinksharingURL string `help:"url link for linksharing requests within the application" default:"https://link.storjsatelliteshare.io" devDefault:"http://localhost:8001"`
PublicLinksharingURL string `help:"url link for linksharing requests for external sharing" default:"https://link.storjshare.io" devDefault:"http://localhost:8001"` PublicLinksharingURL string `help:"url link for linksharing requests for external sharing" default:"https://link.storjshare.io" devDefault:"http://localhost:8001"`
PathwayOverviewEnabled bool `help:"indicates if the overview onboarding step should render with pathways" default:"true"` PathwayOverviewEnabled bool `help:"indicates if the overview onboarding step should render with pathways" default:"true"`
AllProjectsDashboard bool `help:"indicates if all projects dashboard should be used" default:"true"`
LimitsAreaEnabled bool `help:"indicates whether limit card section of the UI is enabled" default:"true"` LimitsAreaEnabled bool `help:"indicates whether limit card section of the UI is enabled" default:"true"`
GeneratedAPIEnabled bool `help:"indicates if generated console api should be used" default:"false"` GeneratedAPIEnabled bool `help:"indicates if generated console api should be used" default:"false"`
OptionalSignupSuccessURL string `help:"optional url to external registration success page" default:""` OptionalSignupSuccessURL string `help:"optional url to external registration success page" default:""`
@ -734,7 +733,6 @@ func (server *Server) frontendConfigHandler(w http.ResponseWriter, r *http.Reque
DefaultPaidStorageLimit: server.config.UsageLimits.Storage.Paid, DefaultPaidStorageLimit: server.config.UsageLimits.Storage.Paid,
DefaultPaidBandwidthLimit: server.config.UsageLimits.Bandwidth.Paid, DefaultPaidBandwidthLimit: server.config.UsageLimits.Bandwidth.Paid,
Captcha: server.config.Captcha, Captcha: server.config.Captcha,
AllProjectsDashboard: server.config.AllProjectsDashboard,
LimitsAreaEnabled: server.config.LimitsAreaEnabled, LimitsAreaEnabled: server.config.LimitsAreaEnabled,
InactivityTimerEnabled: server.config.Session.InactivityTimerEnabled, InactivityTimerEnabled: server.config.Session.InactivityTimerEnabled,
InactivityTimerDuration: server.config.Session.InactivityTimerDuration, InactivityTimerDuration: server.config.Session.InactivityTimerDuration,

View File

@ -181,9 +181,6 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
# server address of the http api gateway and frontend app # server address of the http api gateway and frontend app
# console.address: :10100 # console.address: :10100
# indicates if all projects dashboard should be used
# console.all-projects-dashboard: true
# default duration for AS OF SYSTEM TIME # default duration for AS OF SYSTEM TIME
# console.as-of-system-time-duration: -5m0s # console.as-of-system-time-duration: -5m0s

View File

@ -159,7 +159,8 @@ async function onCreateProjectClick(): Promise<void> {
} }
try { try {
createdProjectId.value = await projectsStore.createProject(project); const newProj = await projectsStore.createProject(project);
createdProjectId.value = newProj.id;
} catch (error) { } catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.CREATE_PROJECT_MODAL); notify.error(error.message, AnalyticsErrorEventSource.CREATE_PROJECT_MODAL);
isLoading.value = false; isLoading.value = false;
@ -176,7 +177,7 @@ async function onCreateProjectClick(): Promise<void> {
bucketsStore.clearS3Data(); bucketsStore.clearS3Data();
if (usersStore.shouldOnboard && configStore.state.config.allProjectsDashboard) { if (usersStore.shouldOnboard) {
analyticsStore.pageVisit(RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path); analyticsStore.pageVisit(RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path);
await router.push(RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path); await router.push(RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path);
return; return;

View File

@ -123,7 +123,7 @@ const plan = computed((): PricingPlanInfo | null => {
watch(plan, () => { watch(plan, () => {
if (!plan.value) { if (!plan.value) {
appStore.removeActiveModal(); appStore.removeActiveModal();
notify.error('No pricing plan has been selected.', null); notify.error('No pricing plan has been selected.');
} }
}); });
@ -143,13 +143,8 @@ function onClose(): void {
if (usersStore.state.settings.onboardingEnd) { if (usersStore.state.settings.onboardingEnd) {
return; return;
} }
if (isSuccess.value) {
if (configStore.state.config.allProjectsDashboard) { if (isSuccess.value) router.push(RouteConfig.AllProjectsDashboard.path);
router.push(RouteConfig.AllProjectsDashboard.path);
return;
}
router.push(RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path);
}
} }
/** /**
@ -187,7 +182,7 @@ async function onCardAdded(token: string): Promise<void> {
// Fetch cards to hide paid tier banner // Fetch cards to hide paid tier banner
await billingStore.getCreditCards(); await billingStore.getCreditCards();
} catch (error) { } catch (error) {
notify.notifyError(error, null); notify.notifyError(error);
} }
isLoading.value = false; isLoading.value = false;

View File

@ -80,11 +80,11 @@
</div> </div>
</div> </div>
</template> </template>
<div v-if="isAllProjectsDashboard && isProjectOwner" tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onProjectDetailsClick" @keyup.enter="onProjectDetailsClick"> <div v-if="isProjectOwner" tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onProjectDetailsClick" @keyup.enter="onProjectDetailsClick">
<SettingsIcon /> <SettingsIcon />
<p class="project-selection__dropdown__link-container__label">Project Settings</p> <p class="project-selection__dropdown__link-container__label">Project Settings</p>
</div> </div>
<div v-if="isAllProjectsDashboard" tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onAllProjectsClick" @keyup.enter="onAllProjectsClick"> <div tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onAllProjectsClick" @keyup.enter="onAllProjectsClick">
<ProjectIcon /> <ProjectIcon />
<p class="project-selection__dropdown__link-container__label">All projects</p> <p class="project-selection__dropdown__link-container__label">All projects</p>
</div> </div>
@ -92,10 +92,6 @@
<PassphraseIcon /> <PassphraseIcon />
<p class="project-selection__dropdown__link-container__label">Manage Passphrase</p> <p class="project-selection__dropdown__link-container__label">Manage Passphrase</p>
</div> </div>
<div v-if="!isAllProjectsDashboard" class="project-selection__dropdown__link-container" @click.stop="onProjectsLinkClick">
<ManageIcon />
<p class="project-selection__dropdown__link-container__label">Manage Projects</p>
</div>
<div class="project-selection__dropdown__link-container" @click.stop="onCreateLinkClick"> <div class="project-selection__dropdown__link-container" @click.stop="onCreateLinkClick">
<CreateProjectIcon /> <CreateProjectIcon />
<p class="project-selection__dropdown__link-container__label">Create new</p> <p class="project-selection__dropdown__link-container__label">Create new</p>
@ -248,7 +244,6 @@ import CheckmarkIcon from '@/../static/images/navigation/checkmark.svg';
import CreateProjectIcon from '@/../static/images/navigation/createProject.svg'; import CreateProjectIcon from '@/../static/images/navigation/createProject.svg';
import InfoIcon from '@/../static/images/navigation/info.svg'; import InfoIcon from '@/../static/images/navigation/info.svg';
import LogoutIcon from '@/../static/images/navigation/logout.svg'; import LogoutIcon from '@/../static/images/navigation/logout.svg';
import ManageIcon from '@/../static/images/navigation/manage.svg';
import PassphraseIcon from '@/../static/images/navigation/passphrase.svg'; import PassphraseIcon from '@/../static/images/navigation/passphrase.svg';
import MenuIcon from '@/../static/images/navigation/menu.svg'; import MenuIcon from '@/../static/images/navigation/menu.svg';
import ProjectIcon from '@/../static/images/navigation/project.svg'; import ProjectIcon from '@/../static/images/navigation/project.svg';
@ -308,13 +303,6 @@ const isProjectOwner = computed((): boolean => {
return usersStore.state.user.id === projectsStore.state.selectedProject.ownerId; return usersStore.state.user.id === projectsStore.state.selectedProject.ownerId;
}); });
/**
* Indicates if all projects dashboard should be used.
*/
const isAllProjectsDashboard = computed((): boolean => {
return configStore.state.config.allProjectsDashboard;
});
/** /**
* Returns user's own projects. * Returns user's own projects.
*/ */
@ -372,16 +360,7 @@ function compareProjects(a: Project, b: Project) {
* Redirects to project dashboard. * Redirects to project dashboard.
*/ */
function onLogoClick(): void { function onLogoClick(): void {
if (isAllProjectsDashboard.value) { router.push(RouteConfig.AllProjectsDashboard.path);
router.push(RouteConfig.AllProjectsDashboard.path);
return;
}
if (route.name === RouteConfig.ProjectDashboard.name) {
return;
}
router.push(RouteConfig.ProjectDashboard.path);
} }
function onNavClick(path: string): void { function onNavClick(path: string): void {
@ -506,19 +485,6 @@ async function onProjectSelected(projectID: string): Promise<void> {
} }
} }
/**
* Route to projects list page.
*/
function onProjectsLinkClick(): void {
if (route.name !== RouteConfig.ProjectsList.name) {
analyticsStore.pageVisit(RouteConfig.ProjectsList.path);
analyticsStore.eventTriggered(AnalyticsEvent.MANAGE_PROJECTS_CLICKED);
router.push(RouteConfig.ProjectsList.path);
}
isProjectDropdownShown.value = false;
}
/** /**
* Route to create project page. * Route to create project page.
*/ */

View File

@ -143,13 +143,6 @@ const isQuickStartDropdownShown = computed((): boolean => {
return appStore.state.activeDropdown === APP_STATE_DROPDOWNS.QUICK_START; return appStore.state.activeDropdown === APP_STATE_DROPDOWNS.QUICK_START;
}); });
/**
* Indicates if all projects dashboard should be used.
*/
const isAllProjectsDashboard = computed((): boolean => {
return configStore.state.config.allProjectsDashboard;
});
/** /**
* On screen resize handler. * On screen resize handler.
*/ */
@ -162,16 +155,7 @@ function onResize(): void {
* Redirects to project dashboard. * Redirects to project dashboard.
*/ */
function onLogoClick(): void { function onLogoClick(): void {
if (isAllProjectsDashboard.value) { router.push(RouteConfig.AllProjectsDashboard.path);
router.push(RouteConfig.AllProjectsDashboard.path);
return;
}
if (route.name === RouteConfig.ProjectDashboard.name) {
return;
}
router.push(RouteConfig.ProjectDashboard.path);
} }
/** /**

View File

@ -78,11 +78,11 @@
</div> </div>
</template> </template>
<div v-if="isAllProjectsDashboard && isProjectOwner" tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onProjectDetailsClick" @keyup.enter="onProjectDetailsClick"> <div v-if="isProjectOwner" tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onProjectDetailsClick" @keyup.enter="onProjectDetailsClick">
<SettingsIcon /> <SettingsIcon />
<p class="project-selection__dropdown__link-container__label">Project Settings</p> <p class="project-selection__dropdown__link-container__label">Project Settings</p>
</div> </div>
<div v-if="isAllProjectsDashboard" tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onAllProjectsClick" @keyup.enter="onAllProjectsClick"> <div tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onAllProjectsClick" @keyup.enter="onAllProjectsClick">
<ProjectIcon /> <ProjectIcon />
<p class="project-selection__dropdown__link-container__label">All projects</p> <p class="project-selection__dropdown__link-container__label">All projects</p>
</div> </div>
@ -90,10 +90,6 @@
<PassphraseIcon /> <PassphraseIcon />
<p class="project-selection__dropdown__link-container__label">Manage Passphrase</p> <p class="project-selection__dropdown__link-container__label">Manage Passphrase</p>
</div> </div>
<div v-if="!isAllProjectsDashboard" tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onProjectsLinkClick" @keyup.enter="onProjectsLinkClick">
<ManageIcon />
<p class="project-selection__dropdown__link-container__label">Manage Projects</p>
</div>
<div tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onCreateLinkClick" @keyup.enter="onCreateLinkClick"> <div tabindex="0" class="project-selection__dropdown__link-container" @click.stop="onCreateLinkClick" @keyup.enter="onCreateLinkClick">
<CreateProjectIcon /> <CreateProjectIcon />
<p class="project-selection__dropdown__link-container__label">Create new project</p> <p class="project-selection__dropdown__link-container__label">Create new project</p>
@ -129,7 +125,6 @@ import ProjectIcon from '@/../static/images/navigation/project.svg';
import ArrowImage from '@/../static/images/navigation/arrowExpandRight.svg'; import ArrowImage from '@/../static/images/navigation/arrowExpandRight.svg';
import CheckmarkIcon from '@/../static/images/navigation/checkmark.svg'; import CheckmarkIcon from '@/../static/images/navigation/checkmark.svg';
import PassphraseIcon from '@/../static/images/navigation/passphrase.svg'; import PassphraseIcon from '@/../static/images/navigation/passphrase.svg';
import ManageIcon from '@/../static/images/navigation/manage.svg';
import CreateProjectIcon from '@/../static/images/navigation/createProject.svg'; import CreateProjectIcon from '@/../static/images/navigation/createProject.svg';
import SettingsIcon from '@/../static/images/navigation/settings.svg'; import SettingsIcon from '@/../static/images/navigation/settings.svg';
@ -176,13 +171,6 @@ const isProjectOwner = computed((): boolean => {
return userStore.state.user.id === projectsStore.state.selectedProject.ownerId; return userStore.state.user.id === projectsStore.state.selectedProject.ownerId;
}); });
/**
* Indicates if all projects dashboard is enabled.
*/
const isAllProjectsDashboard = computed((): boolean => {
return configStore.state.config.allProjectsDashboard;
});
/** /**
* Indicates if dropdown is shown. * Indicates if dropdown is shown.
*/ */
@ -335,19 +323,6 @@ function closeDropdown(): void {
appStore.closeDropdowns(); appStore.closeDropdowns();
} }
/**
* Route to projects list page.
*/
function onProjectsLinkClick(): void {
if (route.name !== RouteConfig.ProjectsList.name) {
analyticsStore.pageVisit(RouteConfig.ProjectsList.path);
analyticsStore.eventTriggered(AnalyticsEvent.MANAGE_PROJECTS_CLICKED);
router.push(RouteConfig.ProjectsList.path);
}
closeDropdown();
}
/** /**
* Route to all projects page. * Route to all projects page.
*/ */

View File

@ -1,176 +0,0 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div v-if="invite" class="banner">
<VLoader v-if="isLoading" class="banner__loader" width="40px" height="40px" />
<span v-if="invites.length > 1" class="banner__count">{{ invites.length }}</span>
<div class="banner__left">
<UsersIcon class="banner__left__icon" />
<span>{{ invite.inviterEmail }} has invited you to the project "{{ invite.projectName }}".</span>
</div>
<div class="banner__right">
<div class="banner__right__links">
<a @click="onJoinClicked">Join Project</a>
<a @click="onDeclineClicked">Decline</a>
</div>
<CloseIcon class="banner__right__close" @click="onCloseClicked" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { ProjectInvitation, ProjectInvitationResponse } from '@/types/projects';
import { useAppStore } from '@/store/modules/appStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useNotify } from '@/utils/hooks';
import { MODALS } from '@/utils/constants/appStatePopUps';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import VLoader from '@/components/common/VLoader.vue';
import UsersIcon from '@/../static/images/notifications/usersIcon.svg';
import CloseIcon from '@/../static/images/notifications/closeSmall.svg';
const analyticsStore = useAnalyticsStore();
const appStore = useAppStore();
const projectsStore = useProjectsStore();
const notify = useNotify();
const isLoading = ref<boolean>(false);
const hidden = ref<Set<ProjectInvitation>>(new Set<ProjectInvitation>());
/**
* Returns a sorted list of non-hidden project member invitation from the store.
*/
const invites = computed((): ProjectInvitation[] => {
return projectsStore.state.invitations
.filter(invite => !hidden.value.has(invite))
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
});
/**
* Returns the first non-hidden project member invitation from the store.
*/
const invite = computed((): ProjectInvitation | null => {
return !invites.value.length ? null : invites.value[0];
});
/**
* Hides the active project member invitation.
* Closes the notification if there are no more invitations.
*/
function onCloseClicked(): void {
if (isLoading.value || !invite.value) return;
hidden.value.add(invite.value);
}
/**
* Displays the Join Project modal.
*/
function onJoinClicked(): void {
if (isLoading.value || !invite.value) return;
projectsStore.selectInvitation(invite.value);
appStore.updateActiveModal(MODALS.joinProject);
}
/**
* Declines the project member invitation.
*/
async function onDeclineClicked(): Promise<void> {
if (isLoading.value || !invite.value) return;
isLoading.value = true;
try {
await projectsStore.respondToInvitation(invite.value.projectID, ProjectInvitationResponse.Decline);
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
} catch (error) {
error.message = `Failed to decline project invitation. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
}
try {
await projectsStore.getUserInvitations();
await projectsStore.getProjects();
} catch (error) {
error.message = `Failed to reload projects and invitations list. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECT_INVITATION);
}
isLoading.value = false;
}
</script>
<style scoped lang="scss">
.banner {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
padding: 16px;
background: var(--c-white);
border: 1px solid var(--c-grey-3);
border-radius: 10px;
box-shadow: 0 7px 20px rgb(0 0 0 / 15%);
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 20px;
&__count {
padding: 2px 8px;
position: absolute;
top: -8px;
left: -8px;
border-radius: 8px;
background-color: var(--c-blue-3);
color: var(--c-white);
}
&__loader {
position: absolute;
inset: 0;
align-items: center;
border-radius: 10px;
background-color: rgb(255 255 255 / 66%);
}
&__left {
display: flex;
align-items: center;
gap: 16px;
&__icon {
flex-shrink: 0;
}
}
&__right {
display: flex;
align-items: center;
gap: 16px;
&__links {
display: flex;
align-items: center;
gap: 23px;
text-align: center;
& a {
color: var(--c-black);
line-height: 22px;
text-decoration: underline !important;
}
}
&__close {
flex-shrink: 0;
cursor: pointer;
}
}
}
</style>

View File

@ -69,16 +69,12 @@ const plans = ref<PricingPlanInfo[]>([
), ),
]); ]);
/* /**
* Loads pricing plan config. * Loads pricing plan config.
*/ */
onBeforeMount(async () => { onBeforeMount(async () => {
const user: User = usersStore.state.user; const user: User = usersStore.state.user;
let nextPath = RouteConfig.OnboardingTour.with(RouteConfig.OverviewStep).path; const nextPath = RouteConfig.AllProjectsDashboard.path;
if (configStore.state.config.allProjectsDashboard) {
nextPath = RouteConfig.AllProjectsDashboard.path;
}
const pricingPkgsEnabled = configStore.state.config.pricingPackagesEnabled; const pricingPkgsEnabled = configStore.state.config.pricingPackagesEnabled;
if (!pricingPkgsEnabled || user.paidTier || !user.partner) { if (!pricingPkgsEnabled || user.paidTier || !user.partner) {
router.push(nextPath); router.push(nextPath);
@ -89,7 +85,7 @@ onBeforeMount(async () => {
try { try {
pkgAvailable = await payments.pricingPackageAvailable(); pkgAvailable = await payments.pricingPackageAvailable();
} catch (error) { } catch (error) {
notify.notifyError(error, null); notify.notifyError(error);
} }
if (!pkgAvailable) { if (!pkgAvailable) {
router.push(nextPath); router.push(nextPath);
@ -100,14 +96,14 @@ onBeforeMount(async () => {
try { try {
config = require('@/components/account/billing/pricingPlans/pricingPlanConfig.json'); config = require('@/components/account/billing/pricingPlans/pricingPlanConfig.json');
} catch { } catch {
notify.error('No pricing plan configuration file.', null); notify.error('No pricing plan configuration file.');
router.push(nextPath); router.push(nextPath);
return; return;
} }
const plan = config[user.partner] as PricingPlanInfo; const plan = config[user.partner] as PricingPlanInfo;
if (!plan) { if (!plan) {
notify.error(`No pricing plan configuration for partner '${user.partner}'.`, null); notify.error(`No pricing plan configuration for partner '${user.partner}'.`);
router.push(nextPath); router.push(nextPath);
return; return;
} }

View File

@ -637,7 +637,7 @@ onMounted(async (): Promise<void> => {
if (!projectID) return; if (!projectID) return;
if (projectsStore.state.selectedProject.ownerId !== usersStore.state.user.id) { if (projectsStore.state.selectedProject.ownerId !== usersStore.state.user.id) {
await router.replace(configStore.state.config.allProjectsDashboard ? RouteConfig.AllProjectsDashboard : RouteConfig.ProjectDashboard.path); await router.replace(RouteConfig.AllProjectsDashboard.path);
return; return;
} }

View File

@ -1,186 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="projects-list">
<div class="projects-list__title-area">
<h2 class="projects-list__title-area__title" aria-roledescription="title">Projects</h2>
<VButton
label="Create Project +"
width="203px"
height="44px"
:on-press="handleCreateProjectClick"
:is-disabled="areProjectsFetching"
/>
</div>
<VLoader
v-if="areProjectsFetching"
width="100px"
height="100px"
class="projects-loader"
/>
<v-table
v-if="projectsPage.projects.length && !areProjectsFetching"
class="projects-list-items"
:limit="projectsPage.limit"
:total-page-count="projectsPage.pageCount"
items-label="projects"
:on-page-change="onPageChange"
:total-items-count="projectsPage.totalCount"
>
<template #head>
<th class="sort-header-container__name-item align-left">Name</th>
<th class="ort-header-container__users-item align-left"># Users</th>
<th class="sort-header-container__date-item align-left">Date Added</th>
</template>
<template #body>
<ProjectsListItem
v-for="(project, key) in projectsPage.projects"
:key="key"
:item-data="project"
:on-click="() => onProjectSelected(project)"
/>
</template>
</v-table>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { RouteConfig } from '@/types/router';
import { Project, ProjectsPage } from '@/types/projects';
import { LocalData } from '@/utils/localData';
import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames';
import { useNotify } from '@/utils/hooks';
import { useProjectMembersStore } from '@/store/modules/projectMembersStore';
import { useBillingStore } from '@/store/modules/billingStore';
import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore';
import { useBucketsStore } from '@/store/modules/bucketsStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useCreateProjectClickHandler } from '@/composables/useCreateProjectClickHandler';
import ProjectsListItem from '@/components/projectsList/ProjectsListItem.vue';
import VTable from '@/components/common/VTable.vue';
import VLoader from '@/components/common/VLoader.vue';
import VButton from '@/components/common/VButton.vue';
const FIRST_PAGE = 1;
const analyticsStore = useAnalyticsStore();
const bucketsStore = useBucketsStore();
const agStore = useAccessGrantsStore();
const pmStore = useProjectMembersStore();
const billingStore = useBillingStore();
const projectsStore = useProjectsStore();
const { handleCreateProjectClick } = useCreateProjectClickHandler();
const notify = useNotify();
const router = useRouter();
const currentPageNumber = ref<number>(1);
const isLoading = ref<boolean>(false);
const areProjectsFetching = ref<boolean>(true);
/**
* Returns projects page from store.
*/
const projectsPage = computed((): ProjectsPage => {
return projectsStore.state.page;
});
/**
* Fetches owned projects page by clicked page number.
* @param page
* @param limit
*/
async function onPageChange(page: number, limit: number): Promise<void> {
currentPageNumber.value = page;
try {
await projectsStore.getOwnedProjects(currentPageNumber.value, limit);
} catch (error) {
notify.error(`Unable to fetch owned projects. ${error.message}`, AnalyticsErrorEventSource.PROJECTS_LIST);
}
}
/**
* Fetches all project related information.
* @param project
*/
async function onProjectSelected(project: Project): Promise<void> {
if (isLoading.value) return;
isLoading.value = true;
const projectID = project.id;
projectsStore.selectProject(projectID);
LocalData.setSelectedProjectId(projectID);
pmStore.setSearchQuery('');
try {
await Promise.all([
pmStore.getProjectMembers(FIRST_PAGE, projectID),
agStore.getAccessGrants(FIRST_PAGE, projectID),
bucketsStore.getBuckets(FIRST_PAGE, projectID),
projectsStore.getProjectLimits(projectID),
]);
billingStore.getProjectUsageAndChargesCurrentRollup().catch((error) => {
notify.notifyError(error, AnalyticsErrorEventSource.PROJECTS_LIST);
});
analyticsStore.pageVisit(RouteConfig.EditProjectDetails.path);
await router.push(RouteConfig.EditProjectDetails.path);
} catch (error) {
error.message = `Unable to select project. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.PROJECTS_LIST);
}
isLoading.value = false;
}
/**
* Lifecycle hook after initial render where list of existing owned projects is fetched.
*/
onMounted(async () => {
try {
await projectsStore.getOwnedProjects(currentPageNumber.value);
areProjectsFetching.value = false;
} catch (error) {
notify.error(`Unable to fetch owned projects. ${error.message}`, AnalyticsErrorEventSource.PROJECTS_LIST);
}
});
</script>
<style lang="scss">
.projects-list {
padding: 40px 30px 55px;
height: calc(100% - 95px);
font-family: 'font_regular', sans-serif;
&__title-area {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 10px;
&__title {
font-family: 'font_bold', sans-serif;
font-size: 22px;
line-height: 27px;
color: #263549;
margin: 10px 0 0;
}
}
.projects-list-items {
margin-top: 40px;
}
}
.projects-loader {
margin-top: 50px;
}
</style>

View File

@ -1,53 +0,0 @@
// Copyright (C) 2021 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<table-item
:item="itemToRender"
:on-click="onClick"
class="container__item"
/>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { Project } from '@/types/projects';
import { useResize } from '@/composables/resize';
import TableItem from '@/components/common/TableItem.vue';
const props = withDefaults(defineProps<{
itemData: Project,
onClick: (project: string) => void,
}>(), {
itemData: () => new Project('default', 'name', 'desc'),
onClick: (_: string) => {},
});
const { isMobile } = useResize();
const itemToRender = computed((): { [key: string]: string | string[] } => {
if (!isMobile.value) {
return {
name: props.itemData.name,
memberCount: props.itemData.memberCount.toString(),
date: props.itemData.createdDate(),
};
}
return { info: [ props.itemData.name, `Created ${props.itemData.createdDate()}` ] };
});
</script>
<style scoped lang="scss">
.container {
&__item {
width: 33%;
font-family: 'font_regular', sans-serif;
font-size: 16px;
margin: 0;
}
}
</style>

View File

@ -197,7 +197,7 @@ async function resendInvites(): Promise<void> {
* Set up listener to clear search bar. * Set up listener to clear search bar.
*/ */
onMounted((): void => { onMounted((): void => {
if (configStore.state.config.allProjectsDashboard && !projectsStore.state.selectedProject.id) { if (!projectsStore.state.selectedProject.id) {
// navigation back to the all projects dashboard is done in ProjectMembersArea.vue // navigation back to the all projects dashboard is done in ProjectMembersArea.vue
return; return;
} }

View File

@ -28,7 +28,6 @@ import OnbCLIStep from '@/components/onboardingTour/steps/CLIStep.vue';
import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue'; import OverviewStep from '@/components/onboardingTour/steps/OverviewStep.vue';
import EditProjectDetails from '@/components/project/EditProjectDetails.vue'; import EditProjectDetails from '@/components/project/EditProjectDetails.vue';
import ProjectDashboard from '@/components/project/dashboard/ProjectDashboard.vue'; import ProjectDashboard from '@/components/project/dashboard/ProjectDashboard.vue';
import ProjectsList from '@/components/projectsList/ProjectsList.vue';
import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue'; import ProjectMembersArea from '@/components/team/ProjectMembersArea.vue';
import CLIInstall from '@/components/onboardingTour/steps/cliFlow/CLIInstall.vue'; import CLIInstall from '@/components/onboardingTour/steps/cliFlow/CLIInstall.vue';
import APIKey from '@/components/onboardingTour/steps/cliFlow/APIKey.vue'; import APIKey from '@/components/onboardingTour/steps/cliFlow/APIKey.vue';
@ -264,11 +263,6 @@ export const router = createRouter({
}, },
], ],
}, },
{
path: RouteConfig.ProjectsList.path,
name: RouteConfig.ProjectsList.name,
component: ProjectsList,
},
{ {
path: RouteConfig.Buckets.path, path: RouteConfig.Buckets.path,
name: RouteConfig.Buckets.name, name: RouteConfig.Buckets.name,
@ -388,7 +382,9 @@ router.beforeEach(async (to, from, next) => {
appStore.toggleHasJustLoggedIn(false); appStore.toggleHasJustLoggedIn(false);
} }
if (!configStore.state.config.billingFeaturesEnabled && to.path.includes(RouteConfig.Billing.path)) { // Config value has to be compared exactly to 'false'
// because value is 'undefined' on initial load (API request is still processing).
if (configStore.state.config.billingFeaturesEnabled === false && to.path.includes(RouteConfig.Billing.path)) {
next(RouteConfig.Account.with(RouteConfig.Settings).path); next(RouteConfig.Account.with(RouteConfig.Settings).path);
return; return;

View File

@ -29,7 +29,6 @@ export class FrontendConfig {
publicLinksharingURL: string; publicLinksharingURL: string;
pathwayOverviewEnabled: boolean; pathwayOverviewEnabled: boolean;
captcha: CaptchaConfig; captcha: CaptchaConfig;
allProjectsDashboard: boolean;
limitsAreaEnabled: boolean; limitsAreaEnabled: boolean;
defaultPaidStorageLimit: MemorySize; defaultPaidStorageLimit: MemorySize;
defaultPaidBandwidthLimit: MemorySize; defaultPaidBandwidthLimit: MemorySize;

View File

@ -25,7 +25,6 @@ export abstract class RouteConfig {
public static CreateProject = new NavigationLink('/create-project', 'Create Project'); public static CreateProject = new NavigationLink('/create-project', 'Create Project');
public static EditProjectDetails = new NavigationLink('/edit-project-details', 'Edit Project Details'); public static EditProjectDetails = new NavigationLink('/edit-project-details', 'Edit Project Details');
public static AccessGrants = new NavigationLink('/access-grants', 'Access'); public static AccessGrants = new NavigationLink('/access-grants', 'Access');
public static ProjectsList = new NavigationLink('/projects', 'Projects');
public static Buckets = new NavigationLink('/buckets', 'Buckets'); public static Buckets = new NavigationLink('/buckets', 'Buckets');
// account child paths // account child paths

View File

@ -206,7 +206,7 @@ const isDropdownShown = ref(false);
const pathEmail = ref<string | null>(null); const pathEmail = ref<string | null>(null);
const inviteInvalid = ref(false); const inviteInvalid = ref(false);
const returnURL = ref(RouteConfig.ProjectDashboard.path); const returnURL = ref(RouteConfig.AllProjectsDashboard.path);
const hcaptcha = ref<VueHcaptcha | null>(null); const hcaptcha = ref<VueHcaptcha | null>(null);
const mfaInput = ref<typeof ConfirmMFAInput & ClearInput | null>(null); const mfaInput = ref<typeof ConfirmMFAInput & ClearInput | null>(null);
@ -267,11 +267,7 @@ onMounted(() => {
isActivatedBannerShown.value = !!route.query.activated; isActivatedBannerShown.value = !!route.query.activated;
isActivatedError.value = route.query.activated === 'false'; isActivatedError.value = route.query.activated === 'false';
if (configStore.state.config.allProjectsDashboard) { if (route.query.return_url) returnURL.value = route.query.return_url as string;
returnURL.value = RouteConfig.AllProjectsDashboard.path;
}
returnURL.value = route.query.return_url as string || returnURL.value;
}); });
/** /**
@ -433,7 +429,7 @@ async function login(): Promise<void> {
if (isMFARequired.value && !(error instanceof ErrorTooManyRequests)) { if (isMFARequired.value && !(error instanceof ErrorTooManyRequests)) {
if (error instanceof ErrorBadRequest || error instanceof ErrorUnauthorized) { if (error instanceof ErrorBadRequest || error instanceof ErrorUnauthorized) {
notify.error(error.message, null); notify.error(error.message);
} }
isMFAError.value = true; isMFAError.value = true;
@ -447,7 +443,7 @@ async function login(): Promise<void> {
return; return;
} }
notify.notifyError(error, null); notify.notifyError(error);
isLoading.value = false; isLoading.value = false;
return; return;
} }

View File

@ -171,7 +171,7 @@ onMounted(async () => {
appStore.changeState(FetchState.LOADED); appStore.changeState(FetchState.LOADED);
if (usersStore.shouldOnboard && !appStore.state.hasShownPricingPlan) { if (usersStore.shouldOnboard && configStore.state.config.pricingPackagesEnabled && !appStore.state.hasShownPricingPlan) {
appStore.setHasShownPricingPlan(true); appStore.setHasShownPricingPlan(true);
// if the user is not legible for a pricing plan, they'll automatically be // if the user is not legible for a pricing plan, they'll automatically be
// navigated back to all projects dashboard. // navigated back to all projects dashboard.

View File

@ -18,8 +18,6 @@
<BetaSatBar v-if="isBetaSatellite" /> <BetaSatBar v-if="isBetaSatellite" />
<MFARecoveryCodeBar v-if="showMFARecoveryCodeBar" :open-generate-modal="toggleMFARecoveryModal" /> <MFARecoveryCodeBar v-if="showMFARecoveryCodeBar" :open-generate-modal="toggleMFARecoveryModal" />
<div class="dashboard__wrap__main-area__content-wrap__container__content banners"> <div class="dashboard__wrap__main-area__content-wrap__container__content banners">
<ProjectInvitationBanner v-if="isProjectInvitationBannerShown" />
<UpgradeNotification <UpgradeNotification
v-if="isPaidTierBannerShown" v-if="isPaidTierBannerShown"
:open-add-p-m-modal="togglePMModal" :open-add-p-m-modal="togglePMModal"
@ -86,7 +84,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref, toRaw } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized'; import { ErrorUnauthorized } from '@/api/errors/ErrorUnauthorized';
@ -119,7 +117,6 @@ import MobileNavigation from '@/components/navigation/MobileNavigation.vue';
import LimitWarningModal from '@/components/modals/LimitWarningModal.vue'; import LimitWarningModal from '@/components/modals/LimitWarningModal.vue';
import VBanner from '@/components/common/VBanner.vue'; import VBanner from '@/components/common/VBanner.vue';
import UpgradeNotification from '@/components/notifications/UpgradeNotification.vue'; import UpgradeNotification from '@/components/notifications/UpgradeNotification.vue';
import ProjectInvitationBanner from '@/components/notifications/ProjectInvitationBanner.vue';
import BrandedLoader from '@/components/common/BrandedLoader.vue'; import BrandedLoader from '@/components/common/BrandedLoader.vue';
import ObjectsUploadingModal from '@/components/modals/objectUpload/ObjectsUploadingModal.vue'; import ObjectsUploadingModal from '@/components/modals/objectUpload/ObjectsUploadingModal.vue';
import LimitWarningBanners from '@/views/dashboard/components/LimitWarningBanners.vue'; import LimitWarningBanners from '@/views/dashboard/components/LimitWarningBanners.vue';
@ -289,13 +286,6 @@ const isLargeUploadWarningNotificationShown = computed((): boolean => {
return appStore.state.isLargeUploadWarningNotificationShown; return appStore.state.isLargeUploadWarningNotificationShown;
}); });
/**
* Indicates whether the project member invitation banner should be shown.
*/
const isProjectInvitationBannerShown = computed((): boolean => {
return !configStore.state.config.allProjectsDashboard;
});
/** /**
* Indicates if current route is the dashboard page. * Indicates if current route is the dashboard page.
*/ */
@ -434,26 +424,7 @@ onMounted(async () => {
return; return;
} }
if (projects.length) { if (projects.length) selectProject(projects);
selectProject(projects);
}
if (!configStore.state.config.allProjectsDashboard) {
try {
if (!projects.length) {
await projectsStore.createDefaultProject(usersStore.state.user.id);
}
const onboardingPath = RouteConfig.OnboardingTour.with(configStore.firstOnboardingStep).path;
if (usersStore.shouldOnboard && route.path !== onboardingPath) {
analyticsStore.pageVisit(onboardingPath);
await router.push(onboardingPath);
}
} catch (error) {
notify.error(error.message, AnalyticsErrorEventSource.OVERALL_APP_WRAPPER_ERROR);
return;
}
}
appStore.changeState(FetchState.LOADED); appStore.changeState(FetchState.LOADED);
}); });

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.24777 0.800049C9.90503 0.800049 10.4378 1.33284 10.4378 1.9901L10.4378 2.18417C10.4378 2.62551 10.7956 2.98329 11.2369 2.9833C11.3772 2.98331 11.515 2.94638 11.6365 2.87625L11.8046 2.7792C12.3738 2.45058 13.1016 2.64561 13.4303 3.21481L14.8584 5.68835C15.1812 6.24756 14.9987 6.9599 14.4524 7.29636L14.4227 7.31401L14.2547 7.41102C13.8915 7.62073 13.767 8.0852 13.9767 8.44845C14.0434 8.56391 14.1392 8.65978 14.2547 8.72644L14.3633 8.78913C14.9325 9.11776 15.1275 9.8456 14.7989 10.4148L13.4303 12.7853C13.1074 13.3445 12.3992 13.5426 11.8347 13.2377L11.8046 13.2209L11.6365 13.1238C11.2543 12.9031 10.7656 13.0341 10.5449 13.4163C10.4747 13.5378 10.4378 13.6756 10.4378 13.8159L10.4378 14.01C10.4379 14.6561 9.92297 15.182 9.28111 15.1996L9.24777 15.2H6.39157C5.73432 15.2 5.2015 14.6672 5.20146 14.01L5.20145 13.919C5.20144 13.5105 4.8703 13.1793 4.46183 13.1794C4.332 13.1794 4.20446 13.2135 4.09203 13.2785L4.01326 13.3239C3.45406 13.6468 2.74172 13.4643 2.40525 12.918L2.38759 12.8884L0.959492 10.4148C0.630894 9.8456 0.825905 9.11777 1.39508 8.7891L1.56321 8.69202C1.94541 8.47134 2.07635 7.98262 1.85567 7.60042C1.78553 7.47894 1.68465 7.37806 1.56316 7.30793L1.39512 7.21092C0.835856 6.88813 0.637769 6.17995 0.942603 5.61541L0.959492 5.58529L2.32809 3.21481C2.65673 2.64561 3.38456 2.45059 3.95378 2.7792L4.06228 2.84184C4.42552 3.05156 4.88999 2.92709 5.09971 2.56385C5.16636 2.44841 5.20145 2.31745 5.20145 2.18415L5.20146 1.99013C5.2015 1.33288 5.73432 0.800049 6.39157 0.800049H9.24777ZM6.51063 2.10902L6.51054 2.18418C6.51054 2.54726 6.41496 2.90394 6.23342 3.21839C5.66902 4.19598 4.42714 4.53859 3.44319 3.99557L3.40237 3.97233L2.15279 6.13662L2.21765 6.17419C2.52213 6.34997 2.77756 6.59894 2.961 6.89818L2.98936 6.94584C3.5647 7.9423 3.23411 9.21322 2.2531 9.80487L2.21779 9.82571L2.15279 9.86324L3.462 12.1309L3.48698 12.1171C3.76948 11.9642 4.08399 11.88 4.40507 11.8711L4.46179 11.8703C5.56725 11.8702 6.46823 12.7458 6.5091 13.8412L6.51039 13.891H9.12881L9.12873 13.8159C9.12872 13.4643 9.21664 13.1186 9.3841 12.8101L9.4112 12.7617C9.98655 11.7652 11.2525 11.4161 12.2554 11.97L12.2911 11.9901L12.3561 12.0275L13.6057 9.86324L13.6001 9.86015C13.3014 9.68766 13.0508 9.44336 12.8708 9.14973L12.843 9.10296C12.2786 8.12536 12.6029 6.87857 13.5651 6.29799L13.6002 6.2773L13.6652 6.2398L12.3561 3.97233L12.291 4.00996C11.9865 4.18574 11.6432 4.28244 11.2923 4.29167L11.2369 4.2924C10.0856 4.29237 9.14994 3.36956 9.12908 2.22313L9.12873 2.18396L9.12881 2.10914L6.51063 2.10902ZM7.92147 4.72732C9.72895 4.72732 11.1942 6.19257 11.1942 8.00005C11.1942 9.80753 9.72895 11.2728 7.92147 11.2728C6.114 11.2728 4.64875 9.80753 4.64875 8.00005C4.64875 6.19257 6.114 4.72732 7.92147 4.72732ZM7.92147 6.03641C6.83699 6.03641 5.95784 6.91556 5.95784 8.00005C5.95784 9.08453 6.83699 9.96369 7.92147 9.96369C9.00596 9.96369 9.88511 9.08453 9.88511 8.00005C9.88511 6.91556 9.00596 6.03641 7.92147 6.03641Z" fill="#56606D"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -1,6 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.1231 25.2746C17.9508 26.9128 17.2608 28.9197 17.2608 31.0878V34.0008H2V32.24C2 26.7291 6.46741 22.2617 11.9782 22.2617C14.7793 22.2617 17.3108 23.4159 19.1231 25.2746Z" fill="#376FFF"/>
<circle cx="12.2244" cy="14.1571" r="7.15706" fill="#00E075"/>
<path d="M18.4346 32.24C18.4346 26.7291 22.902 22.2617 28.4128 22.2617C33.9236 22.2617 38.391 26.7291 38.391 32.24V34.0008H18.4346V32.24Z" fill="#FF598B"/>
<circle cx="28.5828" cy="14.1571" r="7.15706" fill="#FFC700"/>
</svg>

Before

Width:  |  Height:  |  Size: 628 B