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:
parent
5a03e29fca
commit
28711e30ad
@ -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`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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({
|
||||
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user