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,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>
|
||||
|
@ -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>
|
||||
<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>
|
||||
|
@ -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