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,20 +5,21 @@
<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 #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">
<path d="M10.0567 1.05318L10.0849 1.06832L17.4768 5.33727C17.6249 5.42279 17.7084 5.5642 17.7274 5.71277L17.7301 5.73906L17.7314 5.76543L17.7316 14.2407C17.7316 14.4124 17.6452 14.5718 17.5032 14.6657L17.476 14.6826L10.084 18.9322C9.93533 19.0176 9.75438 19.0223 9.60224 18.9463L9.57407 18.9311L2.25387 14.6815C2.10601 14.5956 2.01167 14.4418 2.00108 14.2726L2 14.2407V5.77863L2.00023 5.75974C1.99504 5.58748 2.07759 5.41781 2.22993 5.31793L2.25457 5.30275L9.57482 1.06849C9.71403 0.987969 9.88182 0.978444 10.0278 1.03993L10.0567 1.05318ZM16.7123 6.6615L10.3397 10.3418V17.6094L16.7123 13.9458V6.6615ZM3.01944 6.66573V13.947L9.32037 17.605V10.3402L3.01944 6.66573ZM9.83034 2.09828L3.49475 5.76287L9.83122 9.45833L16.2029 5.7786L9.83034 2.09828Z" fill="currentColor"/>
</svg> -->
</template>
<v-list-item-title link class="text-body-2 ml-3">
Go back
</v-list-item-title>
</v-list-item>
<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">
<path d="M10.0567 1.05318L10.0849 1.06832L17.4768 5.33727C17.6249 5.42279 17.7084 5.5642 17.7274 5.71277L17.7301 5.73906L17.7314 5.76543L17.7316 14.2407C17.7316 14.4124 17.6452 14.5718 17.5032 14.6657L17.476 14.6826L10.084 18.9322C9.93533 19.0176 9.75438 19.0223 9.60224 18.9463L9.57407 18.9311L2.25387 14.6815C2.10601 14.5956 2.01167 14.4418 2.00108 14.2726L2 14.2407V5.77863L2.00023 5.75974C1.99504 5.58748 2.07759 5.41781 2.22993 5.31793L2.25457 5.30275L9.57482 1.06849C9.71403 0.987969 9.88182 0.978444 10.0278 1.03993L10.0567 1.05318ZM16.7123 6.6615L10.3397 10.3418V17.6094L16.7123 13.9458V6.6615ZM3.01944 6.66573V13.947L9.32037 17.605V10.3402L3.01944 6.66573ZM9.83034 2.09828L3.49475 5.76287L9.83122 9.45833L16.2029 5.7786L9.83034 2.09828Z" fill="currentColor"/>
</svg> -->
</template>
<v-list-item-title link class="text-body-2 ml-3">
Go back
</v-list-item-title>
</v-list-item>
<v-divider class="my-2" />
<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>
<default-bar show-nav-drawer-button />
<ProjectNav v-if="appStore.state.isNavigationDrawerShown" />
<default-view />
<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,33 +40,33 @@ 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;
}
if (!projects.some(p => p.id === projectId)) {
router.push('/projects');
return;
}
projectsStore.selectProject(projectId);
// Length of fetchedProjects array is checked before selectProject() function call.
storeProject(fetchedProjects[0].id);
isLoading.value = false;
}
watch(() => route.params.projectId, async newId => selectProject(newId as string));
/**
* Lifecycle hook after initial render.
* Pre fetches user`s and project information.
@ -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,
};
});