Skip to content

Commit

Permalink
web/satellite: use new invite functionality
Browse files Browse the repository at this point in the history
This change uses the new project invite endpoint in place of the former
that adds invited users directly to a project's members. LoginArea
is updated to make the region/email from an invite email link uneditable.
VInput.vue's composition api code has also been updated to match other
components.

Issue: #5741

Change-Id: Ia3f82f5675fba442bb079dc8659b5396a25b9f03
  • Loading branch information
wilfred-asomanii committed Jun 13, 2023
1 parent 09a7d23 commit 62c29ee
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 184 deletions.
42 changes: 19 additions & 23 deletions web/satellite/src/api/projectMembers.ts
Expand Up @@ -3,31 +3,11 @@

import { BaseGql } from '@/api/baseGql';
import { ProjectMember, ProjectMemberCursor, ProjectMembersApi, ProjectMembersPage } from '@/types/projectMembers';
import { HttpClient } from '@/utils/httpClient';

export class ProjectMembersApiGql extends BaseGql implements ProjectMembersApi {

/**
* Used for adding team members to project.
*
* @param projectId
* @param emails
*/
public async add(projectId: string, emails: string[]): Promise<void> {
const query =
`mutation($projectId: String!, $emails:[String!]!) {
addProjectMembers(
publicId: $projectId,
email: $emails
) {publicId}
}`;

const variables = {
projectId,
emails,
};

await this.mutate(query, variables);
}
private readonly http: HttpClient = new HttpClient();
private readonly ROOT_PATH: string = '/api/v0/projects';

/**
* Used for deleting team members from project.
Expand Down Expand Up @@ -106,6 +86,22 @@ export class ProjectMembersApiGql extends BaseGql implements ProjectMembersApi {
return this.getProjectMembersList(response.data.project.members);
}

/**
* Handles inviting users to a project.
*
* @throws Error
*/
public async invite(projectID: string, emails: string[]): Promise<void> {
const path = `${this.ROOT_PATH}/${projectID}/invite`;
const body = { emails };
const httpResponse = await this.http.post(path, JSON.stringify(body));

if (httpResponse.ok) return;

const result = await httpResponse.json();
throw new Error(result.error || 'Failed to send project invitations');
}

/**
* Method for mapping project members page from json to ProjectMembersPage type.
*
Expand Down
226 changes: 107 additions & 119 deletions web/satellite/src/components/common/VInput.vue
Expand Up @@ -66,9 +66,9 @@
</div>
</template>

<script lang="ts">
<script setup lang="ts">
import { computed, defineComponent, onBeforeMount, ref } from 'vue';
import { computed, onBeforeMount, ref, watch } from 'vue';
import ErrorIcon from '@/../static/images/register/ErrorInfo.svg';
import PasswordShownIcon from '@/../static/images/common/passwordShown.svg';
Expand All @@ -77,125 +77,111 @@ import PasswordHiddenIcon from '@/../static/images/common/passwordHidden.svg';
const textType = 'text';
const passwordType = 'password';
export default defineComponent({
name: 'VInput',
components: {
PasswordHiddenIcon,
PasswordShownIcon,
ErrorIcon,
},
props: {
additionalLabel: {
type: String,
default: '',
},
currentLimit: {
type: Number,
default: 0,
},
isOptional: Boolean,
isLimitShown: Boolean,
isMultiline: Boolean,
isLoading: Boolean,
initValue: {
type: String,
default: '',
},
label: {
type: String,
default: '',
},
placeholder: {
type: String,
default: 'default',
},
isPassword: Boolean,
height: {
type: String,
default: '48px',
},
width: {
type: String,
default: '100%',
},
error: {
type: String,
default: '',
const props = withDefaults(defineProps<{
additionalLabel?: string,
initValue?: string,
label?: string,
height?: string,
width?: string,
error?: string,
placeholder?: string,
roleDescription?: string,
currentLimit?: number,
maxSymbols?: number,
isOptional?: boolean,
isLimitShown?: boolean,
isMultiline?: boolean,
isLoading?: boolean,
isPassword?: boolean,
isWhite?: boolean,
withIcon?: boolean,
disabled?: boolean,
}>(), {
additionalLabel: '',
initValue: '',
placeholder: '',
label: '',
error: '',
roleDescription: 'input-container',
height: '48px',
width: '100%',
currentLimit: 0,
maxSymbols: Number.MAX_SAFE_INTEGER,
isOptional: false,
isLimitShown: false,
isLoading: false,
isPassword: false,
isWhite: false,
withIcon: false,
disabled: false,
});
const emit = defineEmits(['showPasswordStrength', 'hidePasswordStrength', 'setData']);
const value = ref('');
const isPasswordShown = ref(false);
const type = ref(textType);
const isPasswordHiddenState = computed(() => {
return props.isPassword && !isPasswordShown.value;
});
const isPasswordShownState = computed(() => {
return props.isPassword && isPasswordShown.value;
});
/**
* Returns style objects depends on props.
*/
const style = computed(() => {
return {
inputStyle: {
width: props.width,
height: props.height,
padding: props.withIcon ? '0 30px 0 50px' : '',
},
maxSymbols: {
type: Number,
default: Number.MAX_SAFE_INTEGER,
labelStyle: {
color: props.isWhite ? 'white' : '#354049',
},
isWhite: Boolean,
withIcon: Boolean,
disabled: Boolean,
roleDescription: {
type: String,
default: 'input-container',
errorStyle: {
color: props.isWhite ? 'white' : '#FF5560',
},
},
emits: ['showPasswordStrength', 'hidePasswordStrength', 'setData'],
setup(props, ctx) {
const value = ref('');
const isPasswordShown = ref(false);
const type = ref(textType);
onBeforeMount(() => {
type.value = props.isPassword ? passwordType : textType;
value.value = props.initValue;
});
return {
isPasswordHiddenState: computed(() => {
return props.isPassword && !isPasswordShown.value;
}),
isPasswordShownState: computed(() => {
return props.isPassword && isPasswordShown.value;
}),
/**
* Returns style objects depends on props.
*/
style: computed(() => {
return {
inputStyle: {
width: props.width,
height: props.height,
padding: props.withIcon ? '0 30px 0 50px' : '',
},
labelStyle: {
color: props.isWhite ? 'white' : '#354049',
},
errorStyle: {
color: props.isWhite ? 'white' : '#FF5560',
},
};
}),
showPasswordStrength(): void {
ctx.emit('showPasswordStrength');
},
hidePasswordStrength(): void {
ctx.emit('hidePasswordStrength');
},
/**
* triggers on input.
*/
onInput(event: Event): void {
const target = event.target as HTMLInputElement;
value.value = target.value;
ctx.emit('setData', value.value);
},
/**
* Triggers input type between text and password to show/hide symbols.
*/
changeVision(): void {
isPasswordShown.value = !isPasswordShown.value;
type.value = isPasswordShown.value ? textType : passwordType;
},
value,
isPasswordShown,
type,
};
},
};
});
function showPasswordStrength(): void {
emit('showPasswordStrength');
}
function hidePasswordStrength(): void {
emit('hidePasswordStrength');
}
/**
* triggers on input.
*/
function onInput(event: Event): void {
const target = event.target as HTMLInputElement;
value.value = target.value;
emit('setData', target.value);
}
/**
* Triggers input type between text and password to show/hide symbols.
*/
function changeVision(): void {
isPasswordShown.value = !isPasswordShown.value;
type.value = isPasswordShown.value ? textType : passwordType;
}
watch(() => props.initValue, (val, oldVal) => {
if (val === oldVal) return;
value.value = val;
});
onBeforeMount(() => {
type.value = props.isPassword ? passwordType : textType;
});
</script>

Expand Down Expand Up @@ -245,12 +231,14 @@ export default defineComponent({
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
line-height: 21px;
color: #354049;
& .add-label {
font-size: 'font_medium' sans-serif;
font-size: 12px;
line-height: 18px;
color: var(--c-grey-5) !important;
}
}
Expand Down
3 changes: 2 additions & 1 deletion web/satellite/src/components/modals/AddTeamMemberModal.vue
Expand Up @@ -24,6 +24,7 @@
placeholder="email@email.com"
role-description="email"
:error="formError"
:max-symbols="72"
@setData="(str) => setInput(index, str)"
/>
</div>
Expand Down Expand Up @@ -192,7 +193,7 @@ async function onAddUsersClick(): Promise<void> {
}
try {
await pmStore.addProjectMembers(emailArray, projectsStore.state.selectedProject.id);
await pmStore.inviteMembers(emailArray, projectsStore.state.selectedProject.id);
} catch (_) {
await notify.error(`Error during adding project members.`, AnalyticsErrorEventSource.ADD_PROJECT_MEMBER_MODAL);
isLoading.value = false;
Expand Down
1 change: 1 addition & 0 deletions web/satellite/src/components/modals/JoinProjectModal.vue
Expand Up @@ -109,6 +109,7 @@ async function respondToInvitation(response: ProjectInvitationResponse): Promise
projectsStore.selectProject(invite.value.projectID);
LocalData.setSelectedProjectId(invite.value.projectID);
notify.success('Invite accepted!');
analytics.pageVisit(RouteConfig.ProjectDashboard.path);
router.push(RouteConfig.ProjectDashboard.path);
}
Expand Down
6 changes: 5 additions & 1 deletion web/satellite/src/components/team/HeaderArea.vue
Expand Up @@ -148,7 +148,11 @@ async function processSearchQuery(search: string): Promise<void> {
pmStore.setSearchQuery(search);
}
try {
await pmStore.getProjectMembers(FIRST_PAGE, projectsStore.state.selectedProject.id);
const id = projectsStore.state.selectedProject.id;
if (!id) {
return;
}
await pmStore.getProjectMembers(FIRST_PAGE, id);
} catch (error) {
notify.error(`Unable to fetch project members. ${error.message}`, AnalyticsErrorEventSource.PROJECT_MEMBERS_HEADER);
}
Expand Down
6 changes: 3 additions & 3 deletions web/satellite/src/store/modules/projectMembersStore.ts
Expand Up @@ -26,8 +26,8 @@ export const useProjectMembersStore = defineStore('projectMembers', () => {

const api: ProjectMembersApi = new ProjectMembersApiGql();

async function addProjectMembers(emails: string[], projectID: string): Promise<void> {
await api.add(projectID, emails);
async function inviteMembers(emails: string[], projectID: string): Promise<void> {
await api.invite(projectID, emails);
}

async function deleteProjectMembers(projectID: string): Promise<void> {
Expand Down Expand Up @@ -109,7 +109,7 @@ export const useProjectMembersStore = defineStore('projectMembers', () => {

return {
state,
addProjectMembers,
inviteMembers,
deleteProjectMembers,
getProjectMembers,
setSearchQuery,
Expand Down
5 changes: 3 additions & 2 deletions web/satellite/src/types/projectMembers.ts
Expand Up @@ -33,15 +33,16 @@ export enum ProjectMemberHeaderState {
* Exposes all ProjectMembers-related functionality
*/
export interface ProjectMembersApi {

/**
* Add members to project by user emails.
* Invite members to project by user emails.
*
* @param projectId
* @param emails list of project members email to add
*
* @throws Error
*/
add(projectId: string, emails: string[]): Promise<void>;
invite(projectId: string, emails: string[]): Promise<void>;

/**
* Deletes ProjectMembers from project by project member emails
Expand Down

0 comments on commit 62c29ee

Please sign in to comment.