web/satellite: add invites to all projects dashboard table
This change adds projects invites to the projects table. Related: https://github.com/storj/storj/issues/5925 Change-Id: I82fbf1e47ea383377369005956a9eec3c8ac6416
This commit is contained in:
parent
771ec3237a
commit
8cdc5bd107
@ -28,6 +28,7 @@
|
||||
<p v-else :class="{primary: index === 0}" :title="val" @click.stop="(e) => cellContentClicked(index, e)">
|
||||
<middle-truncate v-if="keyVal === 'fileName'" :text="val" />
|
||||
<project-ownership-tag v-else-if="keyVal === 'owner'" :no-icon="itemType !== 'project'" :is-owner="val" />
|
||||
<project-ownership-tag v-else-if="keyVal === 'invited'" :is-invited="val" />
|
||||
<span v-else>{{ val }}</span>
|
||||
</p>
|
||||
<div v-if="showBucketGuide(index)" class="animation">
|
||||
|
@ -248,6 +248,14 @@ export class ProjectInvitation {
|
||||
public inviterEmail: string,
|
||||
public createdAt: Date,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns created date as a local string.
|
||||
*/
|
||||
public get invitedDate(): string {
|
||||
const createdAt = new Date(this.createdAt);
|
||||
return createdAt.toLocaleString('en-US', { year: 'numeric', month: '2-digit', day: 'numeric' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,7 +32,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="projects.length || invites.length" class="my-projects__list">
|
||||
<projects-table v-if="isTableViewSelected" class="my-projects__list__table" />
|
||||
<projects-table v-if="isTableViewSelected" :invites="invites" class="my-projects__list__table" />
|
||||
<div v-else-if="!isTableViewSelected" class="my-projects__list__cards">
|
||||
<project-item v-for="project in projects" :key="project.id" :project="project" />
|
||||
<project-invitation-item v-for="invite in invites" :key="invite.projectID" :invitation="invite" />
|
||||
|
@ -0,0 +1,209 @@
|
||||
// Copyright (C) 2023 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
<template>
|
||||
<table-item
|
||||
item-type="project"
|
||||
:item="itemToRender"
|
||||
class="invitation-item"
|
||||
>
|
||||
<template #options>
|
||||
<th class="options overflow-visible">
|
||||
<v-button
|
||||
:loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
:on-press="onJoinClicked"
|
||||
border-radius="8px"
|
||||
font-size="12px"
|
||||
label="Join Project"
|
||||
class="invitation-item__menu__button"
|
||||
/>
|
||||
<div class="invitation-item__menu">
|
||||
<div class="invitation-item__menu__icon" @click.stop="toggleDropDown">
|
||||
<div class="invitation-item__menu__icon__content" :class="{open: isDropdownOpen}">
|
||||
<menu-icon />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isDropdownOpen" v-click-outside="closeDropDown" class="invitation-item__menu__dropdown">
|
||||
<div class="invitation-item__menu__dropdown__item" @click.stop="onDeclineClicked">
|
||||
<logout-icon />
|
||||
<p class="invitation-item__menu__dropdown__item__label">Decline invite</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</template>
|
||||
<menu-icon />
|
||||
</table-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { ProjectInvitation, ProjectInvitationResponse } from '@/types/projects';
|
||||
import { useNotify } from '@/utils/hooks';
|
||||
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
|
||||
import { MODALS } from '@/utils/constants/appStatePopUps';
|
||||
import { useAppStore } from '@/store/modules/appStore';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import { useResize } from '@/composables/resize';
|
||||
import { AnalyticsHttpApi } from '@/api/analytics';
|
||||
|
||||
import VButton from '@/components/common/VButton.vue';
|
||||
import TableItem from '@/components/common/TableItem.vue';
|
||||
|
||||
import MenuIcon from '@/../static/images/common/horizontalDots.svg';
|
||||
import LogoutIcon from '@/../static/images/navigation/logout.svg';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const notify = useNotify();
|
||||
|
||||
const analytics: AnalyticsHttpApi = new AnalyticsHttpApi();
|
||||
|
||||
const isLoading = ref<boolean>(false);
|
||||
|
||||
const props = defineProps<{
|
||||
invitation: ProjectInvitation,
|
||||
}>();
|
||||
|
||||
const { isMobile } = useResize();
|
||||
|
||||
const itemToRender = computed((): { [key: string]: unknown | string[] } => {
|
||||
if (!isMobile.value) {
|
||||
return {
|
||||
multi: { title: props.invitation.projectName, subtitle: props.invitation.projectDescription },
|
||||
date: props.invitation.invitedDate,
|
||||
memberCount: '',
|
||||
invited: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { info: [ props.invitation.projectName, `Created ${props.invitation.invitedDate}` ] };
|
||||
});
|
||||
|
||||
/**
|
||||
* isDropdownOpen if dropdown is open.
|
||||
*/
|
||||
const isDropdownOpen = computed((): boolean => {
|
||||
return appStore.state.activeDropdown === props.invitation.projectID;
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays the Join Project modal.
|
||||
*/
|
||||
function onJoinClicked(): void {
|
||||
projectsStore.selectInvitation(props.invitation);
|
||||
appStore.updateActiveModal(MODALS.joinProject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Declines the project member invitation.
|
||||
*/
|
||||
async function onDeclineClicked(): Promise<void> {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
await projectsStore.respondToInvitation(props.invitation.projectID, ProjectInvitationResponse.Decline);
|
||||
analytics.eventTriggered(AnalyticsEvent.PROJECT_INVITATION_DECLINED);
|
||||
} catch (error) {
|
||||
notify.error(`Failed to decline project invitation. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||
}
|
||||
|
||||
try {
|
||||
await projectsStore.getUserInvitations();
|
||||
await projectsStore.getProjects();
|
||||
} catch (error) {
|
||||
notify.error(`Failed to reload projects and invitations list. ${error.message}`, AnalyticsErrorEventSource.PROJECT_INVITATION);
|
||||
}
|
||||
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
function toggleDropDown() {
|
||||
appStore.toggleActiveDropdown(props.invitation.projectID);
|
||||
}
|
||||
|
||||
function closeDropDown() {
|
||||
appStore.closeDropdowns();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.invitation-item {
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
column-gap: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
&__menu {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&__button {
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
|
||||
&__content {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
padding: 12px 5px;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.open {
|
||||
background: var(--c-grey-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__dropdown {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 7px 20px rgb(0 0 0 / 15%);
|
||||
border: 1px solid var(--c-grey-2);
|
||||
border-radius: 8px;
|
||||
z-index: 100;
|
||||
overflow: hidden;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 200px;
|
||||
padding: 15px;
|
||||
color: var(--c-grey-6);
|
||||
cursor: pointer;
|
||||
|
||||
&__label {
|
||||
font-family: 'font_regular', sans-serif;
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
font-family: 'font_medium', sans-serif;
|
||||
color: var(--c-blue-3);
|
||||
background-color: var(--c-grey-1);
|
||||
|
||||
svg :deep(path) {
|
||||
fill: var(--c-blue-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -206,7 +206,8 @@ async function goToProjectEdit(): Promise<void> {
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 25px;
|
||||
width: 200px;
|
||||
padding: 15px;
|
||||
color: var(--c-grey-6);
|
||||
cursor: pointer;
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<template>
|
||||
<v-table
|
||||
:total-items-count="projects.length"
|
||||
:total-items-count="projects.length + invites?.length || 0"
|
||||
class="projects-table"
|
||||
items-label="projects"
|
||||
>
|
||||
@ -14,6 +14,11 @@
|
||||
<th class="sort-header-container__date-item align-left">Role</th>
|
||||
</template>
|
||||
<template #body>
|
||||
<project-table-invitation-item
|
||||
v-for="(invite, key) in invites"
|
||||
:key="key"
|
||||
:invitation="invite"
|
||||
/>
|
||||
<project-table-item
|
||||
v-for="(project, key) in projects"
|
||||
:key="key"
|
||||
@ -26,14 +31,21 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Project } from '@/types/projects';
|
||||
import { Project, ProjectInvitation } from '@/types/projects';
|
||||
import { useProjectsStore } from '@/store/modules/projectsStore';
|
||||
import ProjectTableItem from '@/views/all-dashboard/components/ProjectTableItem.vue';
|
||||
import ProjectTableInvitationItem from '@/views/all-dashboard/components/ProjectTableInvitationItem.vue';
|
||||
|
||||
import VTable from '@/components/common/VTable.vue';
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
invites?: ProjectInvitation[],
|
||||
}>(), {
|
||||
invites: () => [],
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns projects list from store.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user