-
-
Notifications
You must be signed in to change notification settings - Fork 306
Add Organizations' User Invitation UI #6126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: unstable
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { ref, onMounted } from 'vue'; | ||
| import { OrganizationRoles } from '../constants'; | ||
| import { Organization } from 'shared/data/resources'; | ||
|
|
||
| /** | ||
| * Composable for fetching, creating, and updating a single organization. | ||
| * Pass a getter returning a falsy organizationId to use this in "create a new | ||
| * organization" mode: the fetch is skipped and `create` becomes usable instead | ||
| * of `update`. | ||
| */ | ||
| export function useOrganization(getOrganizationId) { | ||
| const loading = ref(Boolean(getOrganizationId())); | ||
| const organization = ref(null); | ||
|
|
||
| function load() { | ||
| return Organization.fetchModel(getOrganizationId()).then(data => { | ||
| organization.value = data; | ||
| }); | ||
| } | ||
|
|
||
| onMounted(() => { | ||
| if (!getOrganizationId()) { | ||
| return; | ||
| } | ||
| load().finally(() => { | ||
| loading.value = false; | ||
| }); | ||
| }); | ||
|
|
||
| function update(data) { | ||
| return Organization.update(getOrganizationId(), data).then(updated => { | ||
| organization.value = updated; | ||
| return updated; | ||
| }); | ||
| } | ||
|
|
||
| function create(data) { | ||
| return Organization.create(data).then(created => { | ||
| const withAdminRole = { ...created, role: OrganizationRoles.ADMIN }; | ||
| organization.value = withAdminRole; | ||
| return withAdminRole; | ||
| }); | ||
| } | ||
|
|
||
| return { | ||
| loading, | ||
| organization, | ||
| update, | ||
| create, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { ref, onMounted } from 'vue'; | ||
| import { Invitation } from 'shared/data/resources'; | ||
|
|
||
| /** | ||
| * Composable for fetching and responding to organization invitations. | ||
| * | ||
| * @param {Object} params - fetchCollection params, e.g. `{ invited: 1 }` for | ||
| * "invitations addressed to me" (used by the My Organizations banner), or | ||
| * `{ organization: organizationId }` for "pending invites for this org" | ||
| * (used by the org Sharing tab). | ||
| */ | ||
| export function useOrganizationInvitations(params = { invited: 1 }) { | ||
| const loading = ref(true); | ||
| const invitations = ref([]); | ||
|
|
||
| function loadInvitations() { | ||
| return Invitation.fetchCollection(params).then(data => { | ||
| invitations.value = data.filter( | ||
| invitation => | ||
| invitation.organization && | ||
| !invitation.accepted && | ||
| !invitation.declined && | ||
| !invitation.revoked, | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| onMounted(() => { | ||
| loadInvitations().finally(() => { | ||
| loading.value = false; | ||
| }); | ||
| }); | ||
|
|
||
| function accept(invitationId) { | ||
| return Invitation.accept(invitationId).then(() => { | ||
| invitations.value = invitations.value.filter(i => i.id !== invitationId); | ||
| }); | ||
| } | ||
|
|
||
| function decline(invitationId) { | ||
| return Invitation.decline(invitationId).then(() => { | ||
| invitations.value = invitations.value.filter(i => i.id !== invitationId); | ||
| }); | ||
| } | ||
|
|
||
| function revoke(invitationId) { | ||
| return Invitation.revoke(invitationId).then(() => { | ||
| invitations.value = invitations.value.filter(i => i.id !== invitationId); | ||
| }); | ||
| } | ||
|
|
||
| return { | ||
| loading, | ||
| invitations, | ||
| accept, | ||
| decline, | ||
| revoke, | ||
| refresh: loadInvitations, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { ref, onMounted } from 'vue'; | ||
| import { Organization } from 'shared/data/resources'; | ||
|
|
||
| const MAX_PAGE_SIZE = 100; | ||
|
|
||
| /** | ||
| * Composable for fetching the organizations the current user belongs to. | ||
| */ | ||
| export function useOrganizationList() { | ||
| const loading = ref(true); | ||
| const organizations = ref([]); | ||
|
|
||
| onMounted(() => { | ||
| Organization.fetchCollection({ page_size: MAX_PAGE_SIZE, member: true }) | ||
| .then(data => { | ||
| organizations.value = data; | ||
| }) | ||
| .finally(() => { | ||
| loading.value = false; | ||
| }); | ||
| }); | ||
|
|
||
| return { | ||
| loading, | ||
| organizations, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { ref, onMounted } from 'vue'; | ||
| import { OrganizationRoleStatuses } from '../constants'; | ||
| import { OrganizationRole } from 'shared/data/resources'; | ||
|
|
||
| const MAX_PAGE_SIZE = 100; | ||
|
|
||
| /** | ||
| * Composable for fetching and managing an organization's active members. | ||
| */ | ||
| export function useOrganizationMembers(organizationId) { | ||
| const loading = ref(true); | ||
| const members = ref([]); | ||
|
|
||
| function loadMembers() { | ||
| return OrganizationRole.fetchCollection({ | ||
| organization: organizationId, | ||
| status: OrganizationRoleStatuses.ACTIVE, | ||
| page_size: MAX_PAGE_SIZE, | ||
| }).then(data => { | ||
| members.value = data; | ||
| }); | ||
| } | ||
|
|
||
| onMounted(() => { | ||
| loadMembers().finally(() => { | ||
| loading.value = false; | ||
| }); | ||
| }); | ||
|
|
||
| function changeRole(roleId, role) { | ||
| return OrganizationRole.update(roleId, { role }).then(updated => { | ||
| members.value = members.value.map(member => (member.id === roleId ? updated : member)); | ||
| return updated; | ||
| }); | ||
| } | ||
|
|
||
| function close(roleId) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: |
||
| return OrganizationRole.delete(roleId).then(() => { | ||
| members.value = members.value.filter(member => member.id !== roleId); | ||
| }); | ||
| } | ||
|
|
||
| return { | ||
| loading, | ||
| members, | ||
| changeRole, | ||
| close, | ||
| refresh: loadMembers, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export function getApiErrorMessage(error, fallback) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Resolved — addressed in the current code. suggestion: |
||
| const data = error && error.response && error.response.data; | ||
| const message = Array.isArray(data) ? data[0] : null; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: |
||
| return message || fallback; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: No
.catch— a failed fetch leavesorganizationnull, indistinguishable from empty. Same inuseOrganizationList.js:14,useOrganizationMembers.js:24,useOrganizationInvitations.js:28, all unhandled (client.js:113). CompareuseChannelList.js:32-46.