web/satellite/vuetify-poc: invite through all projects dashboard

This change allows project owners to invite members through the all
projects dashboard.

Resolves #6394

Change-Id: Id36d21432ab7b18532679e900d3e00c52fa21fc9
This commit is contained in:
Jeremy Wharton 2023-10-07 23:01:05 -05:00 committed by Jeremy Wharton
parent 0cc04208db
commit dd3779a623
4 changed files with 185 additions and 172 deletions

View File

@ -60,7 +60,7 @@
<v-divider class="my-2" />
<v-list-item link class="mt-1" rounded="lg">
<v-list-item link class="mt-1" rounded="lg" @click="emit('inviteClick')">
<template #prepend>
<icon-team size="18" />
</template>
@ -112,8 +112,9 @@ const props = defineProps<{
}>();
const emit = defineEmits<{
'joinClick': [];
'createClick': [];
joinClick: [];
createClick: [];
inviteClick: [];
}>();
const analyticsStore = useAnalyticsStore();

View File

@ -0,0 +1,162 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<v-dialog
v-model="model"
width="auto"
max-width="420px"
transition="fade-transition"
:persistent="isLoading"
>
<v-card rounded="xlg">
<v-sheet>
<v-card-item class="pl-7 py-4">
<template #prepend>
<v-card-title class="font-weight-bold">
Add Members
</v-card-title>
</template>
<template #append>
<v-btn
icon="$close"
variant="text"
size="small"
color="default"
:disabled="isLoading"
@click="model = false"
/>
</template>
</v-card-item>
</v-sheet>
<v-divider />
<v-form v-model="valid" class="pa-7 pb-4" @submit.prevent="onAddUsersClick">
<v-row>
<v-col cols="12">
<p class="mb-5">Invite team members to join you in this project.</p>
<v-alert
variant="tonal"
color="info"
title="Important Information"
text="All team members should use the same passphrase to access the same data."
rounded="lg"
density="comfortable"
border
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="email"
variant="outlined"
:rules="emailRules"
label="Enter e-mail"
hint="Members will have read & write permissions."
required
autofocus
class="my-2"
/>
</v-col>
</v-row>
</v-form>
<v-divider />
<v-card-actions class="pa-7">
<v-row>
<v-col>
<v-btn variant="outlined" color="default" block :disabled="isLoading" @click="model = false">Cancel</v-btn>
</v-col>
<v-col>
<v-btn color="primary" variant="flat" block :loading="isLoading" @click="onAddUsersClick">Send Invite</v-btn>
</v-col>
</v-row>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import {
VDialog,
VCard,
VSheet,
VCardItem,
VCardTitle,
VBtn,
VDivider,
VForm,
VRow,
VCol,
VAlert,
VTextField,
VCardActions,
} from 'vuetify/components';
import { RequiredRule, ValidationRule } from '@poc/types/common';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useProjectMembersStore } from '@/store/modules/projectMembersStore';
import { useNotify } from '@/utils/hooks';
import { useLoading } from '@/composables/useLoading';
const props = defineProps<{
modelValue: boolean,
projectId: string,
}>();
const emit = defineEmits<{
'update:modelValue': [value: boolean],
}>();
const model = computed<boolean>({
get: () => props.modelValue,
set: value => emit('update:modelValue', value),
});
const analyticsStore = useAnalyticsStore();
const pmStore = useProjectMembersStore();
const notify = useNotify();
const { isLoading, withLoading } = useLoading();
const valid = ref<boolean>(false);
const email = ref<string>('');
const emailRules: ValidationRule<string>[] = [
RequiredRule,
v => ((/.+@.+\..+/.test(v)) || 'E-mail must be valid.'),
];
/**
* Sends a project invitation to the input email.
*/
async function onAddUsersClick(): Promise<void> {
if (!valid.value) return;
await withLoading(async () => {
try {
await pmStore.inviteMembers([email.value], props.projectId);
notify.notify('Invites sent!');
email.value = '';
} catch (error) {
error.message = `Error adding project members. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
return;
}
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_MEMBERS_INVITE_SENT);
try {
await pmStore.getProjectMembers(1, props.projectId);
} catch (error) {
error.message = `Unable to fetch project members. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
}
model.value = false;
});
}
</script>

View File

@ -91,7 +91,7 @@
<ProjectCard class="h-100" @create-click="isCreateProjectDialogShown = true" />
</v-col>
<v-col v-for="item in items" v-else :key="item.id" cols="12" sm="6" md="4" lg="3">
<ProjectCard :item="item" class="h-100" @join-click="onJoinClicked(item)" />
<ProjectCard :item="item" class="h-100" @join-click="onJoinClicked(item)" @invite-click="onInviteClicked(item)" />
</v-col>
</v-row>
</v-container>
@ -103,6 +103,7 @@
:name="joiningItem.name"
/>
<create-project-dialog v-model="isCreateProjectDialogShown" />
<add-team-member-dialog v-model="isAddMemberDialogShown" :project-id="addMemberProjectId" />
</template>
<script setup lang="ts">
@ -112,15 +113,6 @@ import {
VRow,
VCol,
VBtn,
VDialog,
VCard,
VSheet,
VCardItem,
VCardTitle,
VDivider,
VForm,
VTextField,
VCardActions,
VSpacer,
VBtnToggle,
VProgressCircular,
@ -137,24 +129,19 @@ import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
import ProjectsTableComponent from '@poc/components/ProjectsTableComponent.vue';
import JoinProjectDialog from '@poc/components/dialogs/JoinProjectDialog.vue';
import CreateProjectDialog from '@poc/components/dialogs/CreateProjectDialog.vue';
import AddTeamMemberDialog from '@poc/components/dialogs/AddTeamMemberDialog.vue';
const appStore = useAppStore();
const projectsStore = useProjectsStore();
const usersStore = useUsersStore();
const dialog = ref<boolean>(false);
const valid = ref<boolean>(false);
const name = ref<string>('');
const isLoading = ref<boolean>(true);
const joiningItem = ref<ProjectItemModel | null>(null);
const isJoinProjectDialogShown = ref<boolean>(false);
const isCreateProjectDialogShown = ref<boolean>(false);
const nameRules = [
value => (!!value || 'Project name is required.'),
value => ((value?.length <= 100) || 'Name must be less than 100 characters.'),
];
const addMemberProjectId = ref<string>('');
const isAddMemberDialogShown = ref<boolean>(false);
/**
* Returns whether to use the table view.
@ -207,6 +194,14 @@ function onJoinClicked(item: ProjectItemModel): void {
isJoinProjectDialogShown.value = true;
}
/**
* Displays the Add Members dialog.
*/
function onInviteClicked(item: ProjectItemModel): void {
addMemberProjectId.value = item.id;
isAddMemberDialogShown.value = true;
}
onMounted(async (): Promise<void> => {
await usersStore.getUser().catch(_ => {});
await projectsStore.getProjects().catch(_ => {});

View File

@ -11,97 +11,11 @@
<v-col class="pb-0">
<v-row class="mt-2 mb-0">
<v-btn>
<v-btn @click="isAddMemberDialogShown = true">
<svg width="16" height="16" class="mr-2" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 1C14.9706 1 19 5.02944 19 10C19 14.9706 14.9706 19 10 19C5.02944 19 1 14.9706 1 10C1 5.02944 5.02944 1 10 1ZM10 2.65C5.94071 2.65 2.65 5.94071 2.65 10C2.65 14.0593 5.94071 17.35 10 17.35C14.0593 17.35 17.35 14.0593 17.35 10C17.35 5.94071 14.0593 2.65 10 2.65ZM10.7496 6.8989L10.7499 6.91218L10.7499 9.223H12.9926C13.4529 9.223 13.8302 9.58799 13.8456 10.048C13.8602 10.4887 13.5148 10.8579 13.0741 10.8726L13.0608 10.8729L10.7499 10.873L10.75 13.171C10.75 13.6266 10.3806 13.996 9.925 13.996C9.48048 13.996 9.11807 13.6444 9.10066 13.2042L9.1 13.171L9.09985 10.873H6.802C6.34637 10.873 5.977 10.5036 5.977 10.048C5.977 9.60348 6.32857 9.24107 6.76882 9.22366L6.802 9.223H9.09985L9.1 6.98036C9.1 6.5201 9.46499 6.14276 9.925 6.12745C10.3657 6.11279 10.7349 6.45818 10.7496 6.8989Z" fill="currentColor" />
</svg>
Add Members
<v-dialog
v-model="dialog"
activator="parent"
width="auto"
max-width="420px"
transition="fade-transition"
>
<v-card rounded="xlg">
<v-sheet>
<v-card-item class="pl-7 py-4">
<template #prepend>
<v-card-title class="font-weight-bold">
<!-- <v-icon>
<img src="../assets/icon-team.svg" alt="Team">
</v-icon> -->
Add Members
</v-card-title>
</template>
<!-- <v-btn
class="text-none text-subtitle-1"
color="#5865f2"
size="small"
variant="flat"
>
+ Add More
</v-btn> -->
<template #append>
<v-btn
icon="$close"
variant="text"
size="small"
color="default"
@click="dialog = false"
/>
</template>
</v-card-item>
</v-sheet>
<v-divider />
<v-form v-model="valid" class="pa-7 pb-4" @submit.prevent="onAddUsersClick">
<v-row>
<v-col cols="12">
<p class="mb-5">Invite team members to join you in this project.</p>
<v-alert
variant="tonal"
color="info"
title="Important Information"
text="All team members should use the same passphrase to access the same data."
rounded="lg"
density="comfortable"
border
/>
</v-col>
<v-col cols="12">
<v-text-field
v-model="email"
variant="outlined"
:rules="emailRules"
label="Enter e-mail"
hint="Members will have read & write permissions."
required
autofocus
class="my-2"
/>
</v-col>
</v-row>
</v-form>
<v-divider />
<v-card-actions class="pa-7">
<v-row>
<v-col>
<v-btn variant="outlined" color="default" block @click="dialog = false">Cancel</v-btn>
</v-col>
<v-col>
<v-btn color="primary" variant="flat" block :loading="isLoading" @click="onAddUsersClick">Send Invite</v-btn>
</v-col>
</v-row>
</v-card-actions>
</v-card>
</v-dialog>
</v-btn>
<v-btn
v-if="selectedMembers.length"
@ -118,92 +32,33 @@
<TeamTableComponent ref="tableComponent" v-model="selectedMembers" />
</v-container>
<add-team-member-dialog v-model="isAddMemberDialogShown" :project-id="selectedProjectID" />
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import {
VContainer,
VCol,
VRow,
VBtn,
VDialog,
VCard,
VSheet,
VCardItem,
VCardTitle,
VDivider,
VForm,
VCardActions,
VTextField,
VAlert,
} from 'vuetify/components';
import { VContainer, VCol, VRow, VBtn } from 'vuetify/components';
import { useProjectMembersStore } from '@/store/modules/projectMembersStore';
import { useProjectsStore } from '@/store/modules/projectsStore';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { useAnalyticsStore } from '@/store/modules/analyticsStore';
import { useNotify } from '@/utils/hooks';
import PageTitleComponent from '@poc/components/PageTitleComponent.vue';
import PageSubtitleComponent from '@poc/components/PageSubtitleComponent.vue';
import TeamTableComponent from '@poc/components/TeamTableComponent.vue';
import AddTeamMemberDialog from '@poc/components/dialogs/AddTeamMemberDialog.vue';
interface DeleteDialog {
showDeleteDialog(): void;
}
const analyticsStore = useAnalyticsStore();
const pmStore = useProjectMembersStore();
const projectsStore = useProjectsStore();
const notify = useNotify();
const isLoading = ref<boolean>(false);
const dialog = ref<boolean>(false);
const valid = ref<boolean>(false);
const email = ref<string>('');
const selectedMembers = ref<string[]>([]);
const tableComponent = ref<InstanceType<typeof TeamTableComponent> & DeleteDialog>();
const emailRules = [
(value: string): string | boolean => (!!value || 'E-mail is requred.'),
(value: string): string | boolean => ((/.+@.+\..+/.test(value)) || 'E-mail must be valid.'),
];
const isAddMemberDialogShown = ref<boolean>(false);
const selectedProjectID = computed((): string => projectsStore.state.selectedProject.id);
/**
* Tries to add users related to entered emails list to current project.
*/
async function onAddUsersClick(): Promise<void> {
if (isLoading.value || !valid.value) return;
isLoading.value = true;
try {
await pmStore.inviteMembers([email.value], selectedProjectID.value);
notify.notify('Invites sent!');
email.value = '';
} catch (error) {
error.message = `Error adding project members. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
isLoading.value = false;
return;
}
analyticsStore.eventTriggered(AnalyticsEvent.PROJECT_MEMBERS_INVITE_SENT);
try {
await pmStore.getProjectMembers(1, selectedProjectID.value);
} catch (error) {
error.message = `Unable to fetch project members. ${error.message}`;
notify.notifyError(error, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
}
dialog.value = false;
isLoading.value = false;
}
/**
* Makes delete project members dialog visible.
*/