web/satellite/vuetify-poc: add project ID to project-specific routes

A project ID path parameter has been incorporated into project-specific
routes. Previously, project IDs were stored only in local storage,
which made it impossible to bookmark or share links to pages within a
particular project.

Resolves #6076

Change-Id: I4f46f58a95dfc9b67d95fdb1c6a65b50fcafe02b
This commit is contained in:
Jeremy Wharton 2023-07-23 01:48:31 -05:00 committed by Storj Robot
parent 5a03e29fca
commit 28711e30ad
9 changed files with 117 additions and 102 deletions

View File

@ -98,7 +98,6 @@ import { ProjectItemModel, PROJECT_ROLE_COLORS } from '@poc/types/projects';
import { ProjectInvitationResponse } from '@/types/projects';
import { ProjectRole } from '@/types/projectMembers';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { LocalData } from '@/utils/localData';
import IconProject from '@poc/components/icons/IconProject.vue';
import IconSettings from '@poc/components/icons/IconSettings.vue';
@ -123,8 +122,7 @@ const isDeclining = ref<boolean>(false);
function openProject(): void {
if (!props.item) return;
projectsStore.selectProject(props.item.id);
LocalData.setSelectedProjectId(props.item.id);
router.push('/dashboard');
router.push(`/projects/${props.item.id}/dashboard`);
}
/**

View File

@ -130,7 +130,6 @@ import { ProjectInvitationResponse } from '@/types/projects';
import { ProjectRole } from '@/types/projectMembers';
import { SHORT_MONTHS_NAMES } from '@/utils/constants/date';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { LocalData } from '@/utils/localData';
import IconSettings from '@poc/components/icons/IconSettings.vue';
import IconTeam from '@poc/components/icons/IconTeam.vue';
@ -170,8 +169,7 @@ function getFormattedDate(date: Date): string {
*/
function openProject(item: ProjectItemModel): void {
projectsStore.selectProject(item.id);
LocalData.setSelectedProjectId(item.id);
router.push('/dashboard');
router.push(`/projects/${item.id}/dashboard`);
}
/**

View File

@ -77,7 +77,6 @@ import {
import { ProjectInvitationResponse } from '@/types/projects';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { LocalData } from '@/utils/localData';
const props = defineProps<{
modelValue: boolean,
@ -105,8 +104,7 @@ const isDeclining = ref<boolean>(false);
*/
function openProject(): void {
projectsStore.selectProject(props.id);
LocalData.setSelectedProjectId(props.id);
router.push('/dashboard');
router.push(`/projects/${props.id}/dashboard`);
}
/**

View File

@ -5,8 +5,8 @@
<v-navigation-drawer class="py-1">
<v-sheet>
<v-list class="px-2" color="default" variant="flat">
<!-- Project -->
<v-list-item class="pa-4 rounded-lg" link router-link to="/dashboard">
<template v-if="pathBeforeAccountPage">
<v-list-item class="pa-4 rounded-lg" link router-link :to="pathBeforeAccountPage">
<template #prepend>
<img src="@poc/assets/icon-back-tonal.svg" alt="Project">
<!-- <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -19,6 +19,7 @@
</v-list-item>
<v-divider class="my-2" />
</template>
<!-- All Projects -->
<v-list-item class="pa-4 rounded-lg" link router-link to="/projects">
@ -39,7 +40,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item link router-link to="/account-settings" class="my-1 py-3" rounded="lg">
<v-list-item link router-link to="settings" class="my-1 py-3" rounded="lg">
<template #prepend>
<!-- <img src="@poc/assets/icon-settings.svg" alt="Account Settings" class="mr-3"> -->
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -51,7 +52,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item link router-link to="/billing" class="my-1" rounded="lg">
<v-list-item link router-link to="billing" class="my-1" rounded="lg">
<template #prepend>
<!-- <img src="@poc/assets/icon-card.svg" alt="Billing" class="mr-3"> -->
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
@ -70,6 +71,7 @@
</template>
<script setup lang="ts">
import { computed } from 'vue';
import {
VNavigationDrawer,
VSheet,
@ -78,4 +80,17 @@ import {
VListItemTitle,
VDivider,
} from 'vuetify/components';
import { useAppStore } from '@poc/store/appStore';
const appStore = useAppStore();
/**
* Returns the path to the most recent non-account-related page.
*/
const pathBeforeAccountPage = computed((): string | null => {
const path = appStore.state.pathBeforeAccountPage;
if (!path || path === '/projects') return null;
return path;
});
</script>

View File

@ -107,7 +107,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item link class="my-1 rounded-lg" router-link to="/billing">
<v-list-item link class="my-1 rounded-lg" router-link to="/account/billing">
<template #prepend>
<img src="@poc/assets/icon-card.svg" alt="Billing">
</template>
@ -116,7 +116,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item link class="my-1 rounded-lg" router-link to="/account-settings">
<v-list-item link class="my-1 rounded-lg" router-link to="/account/settings">
<template #prepend>
<img src="@poc/assets/icon-settings.svg" alt="Account Settings">
</template>

View File

@ -3,16 +3,21 @@
<template>
<v-app>
<div v-if="isLoading" class="d-flex align-center justify-center w-100 h-100">
<v-progress-circular color="primary" indeterminate size="64" />
</div>
<template v-else>
<default-bar show-nav-drawer-button />
<ProjectNav v-if="appStore.state.isNavigationDrawerShown" />
<default-view />
</template>
</v-app>
</template>
<script setup lang="ts">
import { onBeforeMount } from 'vue';
import { onBeforeRouteLeave, useRouter } from 'vue-router';
import { VApp } from 'vuetify/components';
import { onBeforeMount, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { VApp, VProgressCircular } from 'vuetify/components';
import DefaultBar from './AppBar.vue';
import ProjectNav from './ProjectNav.vue';
@ -24,10 +29,10 @@ import { useBillingStore } from '@/store/modules/billingStore';
import { useUsersStore } from '@/store/modules/usersStore';
import { useABTestingStore } from '@/store/modules/abTestingStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { LocalData } from '@/utils/localData';
import { useAppStore } from '@poc/store/appStore';
const router = useRouter();
const route = useRoute();
const billingStore = useBillingStore();
const usersStore = useUsersStore();
@ -35,32 +40,32 @@ const abTestingStore = useABTestingStore();
const projectsStore = useProjectsStore();
const appStore = useAppStore();
/**
* Stores project to vuex store and browser's local storage.
* @param projectID - project id string
*/
function storeProject(projectID: string): void {
projectsStore.selectProject(projectID);
LocalData.setSelectedProjectId(projectID);
}
const isLoading = ref<boolean>(true);
/**
* Checks if stored project is in fetched projects array and selects it.
* Selects first fetched project if check is not successful.
* @param fetchedProjects - fetched projects array
* Selects the project with the given ID, redirecting to the
* all projects dashboard if no such project exists.
*/
function selectProject(fetchedProjects: Project[]): void {
const storedProjectID = LocalData.getSelectedProjectId();
const isProjectInFetchedProjects = fetchedProjects.some(project => project.id === storedProjectID);
if (storedProjectID && isProjectInFetchedProjects) {
storeProject(storedProjectID);
async function selectProject(projectId: string): Promise<void> {
isLoading.value = true;
let projects: Project[];
try {
projects = await projectsStore.getProjects();
} catch (_) {
router.push('/projects');
return;
}
// Length of fetchedProjects array is checked before selectProject() function call.
storeProject(fetchedProjects[0].id);
if (!projects.some(p => p.id === projectId)) {
router.push('/projects');
return;
}
projectsStore.selectProject(projectId);
isLoading.value = false;
}
watch(() => route.params.projectId, async newId => selectProject(newId as string));
/**
* Lifecycle hook after initial render.
@ -87,16 +92,6 @@ onBeforeMount(async () => {
await billingStore.getCreditCards();
} catch (error) { /* empty */ }
let projects: Project[] = [];
try {
projects = await projectsStore.getProjects();
} catch (error) {
return;
}
if (projects.length) {
selectProject(projects);
}
selectProject(route.params.projectId as string);
});
</script>

View File

@ -117,7 +117,7 @@
<v-divider class="my-2" />
<v-list-item link router-link to="/dashboard" class="my-1 py-3" rounded="lg">
<v-list-item link router-link to="dashboard" class="my-1 py-3" rounded="lg">
<template #prepend>
<IconDashboard />
</template>
@ -126,7 +126,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item link router-link to="/buckets" class="my-1" rounded="lg">
<v-list-item link router-link to="buckets" class="my-1" rounded="lg">
<template #prepend>
<IconBucket />
</template>
@ -135,7 +135,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item link router-link to="/bucket" class="my-1" rounded="lg">
<v-list-item link router-link to="bucket" class="my-1" rounded="lg">
<template #prepend>
<IconBrowse />
</template>
@ -144,7 +144,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item link router-link to="/access" class="my-1" rounded="lg">
<v-list-item link router-link to="access" class="my-1" rounded="lg">
<template #prepend>
<IconAccess />
</template>
@ -153,7 +153,7 @@
</v-list-item-title>
</v-list-item>
<v-list-item link router-link to="/team" class="my-1" rounded="lg">
<v-list-item link router-link to="team" class="my-1" rounded="lg">
<template #prepend>
<IconTeam />
</template>

View File

@ -1,58 +1,32 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
// Composables
import { createRouter, createWebHistory } from 'vue-router';
import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router';
const routes = [
import { useAppStore } from '@poc/store/appStore';
const routes: RouteRecordRaw[] = [
{
path: '/vuetifypoc',
redirect: { path: '/projects' }, // redirect
component: () => import('@poc/layouts/default/Default.vue'),
children: [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import(/* webpackChunkName: "home" */ '@poc/views/Dashboard.vue'),
},
{
path: '/buckets',
name: 'Buckets',
component: () => import(/* webpackChunkName: "Buckets" */ '@poc/views/Buckets.vue'),
},
{
path: '/bucket',
name: 'Bucket',
component: () => import(/* webpackChunkName: "Buckets" */ '@poc/views/Bucket.vue'),
},
{
path: '/access',
name: 'Access',
component: () => import(/* webpackChunkName: "Access" */ '@poc/views/Access.vue'),
},
{
path: '/team',
name: 'Team',
component: () => import(/* webpackChunkName: "Team" */ '@poc/views/Team.vue'),
},
],
},
{
path: '/account',
component: () => import('@poc/layouts/default/Account.vue'),
beforeEnter: (_, from) => useAppStore().setPathBeforeAccountPage(from.path),
children: [
{
path: '/billing',
path: 'billing',
name: 'Billing',
component: () => import(/* webpackChunkName: "Billing" */ '@poc/views/Billing.vue'),
},
{
path: '/account-settings',
path: 'settings',
name: 'Account Settings',
component: () => import(/* webpackChunkName: "MyAccount" */ '@poc/views/AccountSettings.vue'),
},
{
path: '/design-library',
path: 'design-library',
name: 'Design Library',
component: () => import(/* webpackChunkName: "DesignLibrary" */ '@poc/views/DesignLibrary.vue'),
},
@ -63,12 +37,43 @@ const routes = [
component: () => import('@poc/layouts/default/AllProjects.vue'),
children: [
{
path: '/projects',
path: '',
name: 'Projects',
component: () => import(/* webpackChunkName: "Projects" */ '@poc/views/Projects.vue'),
},
],
},
{
path: '/projects/:projectId',
component: () => import('@poc/layouts/default/Default.vue'),
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import(/* webpackChunkName: "home" */ '@poc/views/Dashboard.vue'),
},
{
path: 'buckets',
name: 'Buckets',
component: () => import(/* webpackChunkName: "Buckets" */ '@poc/views/Buckets.vue'),
},
{
path: 'bucket',
name: 'Bucket',
component: () => import(/* webpackChunkName: "Buckets" */ '@poc/views/Bucket.vue'),
},
{
path: 'access',
name: 'Access',
component: () => import(/* webpackChunkName: "Access" */ '@poc/views/Access.vue'),
},
{
path: 'team',
name: 'Team',
component: () => import(/* webpackChunkName: "Team" */ '@poc/views/Team.vue'),
},
],
},
];
const router = createRouter({

View File

@ -6,6 +6,7 @@ import { reactive } from 'vue';
class AppState {
public isNavigationDrawerShown = true;
public pathBeforeAccountPage: string | null = null;
}
export const useAppStore = defineStore('vuetifyApp', () => {
@ -15,8 +16,13 @@ export const useAppStore = defineStore('vuetifyApp', () => {
state.isNavigationDrawerShown = !state.isNavigationDrawerShown;
}
function setPathBeforeAccountPage(path: string) {
state.pathBeforeAccountPage = path;
}
return {
state,
toggleNavigationDrawer,
setPathBeforeAccountPage,
};
});