fix(frontend): Loading and removal of members of organization
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import type { LegalRole } from '~/server/utils/permissions'
|
import type { LegalRole } from '~/server/utils/permissions'
|
||||||
|
import type { ListMembersOptions } from '~/composables/useAuth'
|
||||||
|
|
||||||
export function useOrganizationApi() {
|
export function useOrganizationApi() {
|
||||||
const { organization } = useAuth()
|
const { organization } = useAuth()
|
||||||
@@ -23,6 +24,10 @@ export function useOrganizationApi() {
|
|||||||
return organization.inviteMember({ email, role })
|
return organization.inviteMember({ email, role })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeMember(memberIdOrEmail: string) {
|
||||||
|
return organization.removeMember({ memberIdOrEmail })
|
||||||
|
}
|
||||||
|
|
||||||
async function acceptInvitation(invitationId: string) {
|
async function acceptInvitation(invitationId: string) {
|
||||||
return organization.acceptInvitation({ invitationId })
|
return organization.acceptInvitation({ invitationId })
|
||||||
}
|
}
|
||||||
@@ -47,17 +52,23 @@ export function useOrganizationApi() {
|
|||||||
return organization.setActive({ organizationId })
|
return organization.setActive({ organizationId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function listMembers(options?: ListMembersOptions) {
|
||||||
|
return organization.listMembers(options)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createOrganization,
|
createOrganization,
|
||||||
deleteOrganization,
|
deleteOrganization,
|
||||||
getInvitation,
|
getInvitation,
|
||||||
listInvitations,
|
listInvitations,
|
||||||
inviteMember,
|
inviteMember,
|
||||||
|
removeMember,
|
||||||
acceptInvitation,
|
acceptInvitation,
|
||||||
cancelSentInvitation,
|
cancelSentInvitation,
|
||||||
rejectInvitation,
|
rejectInvitation,
|
||||||
loadOrganizations,
|
loadOrganizations,
|
||||||
checkSlugAvailability,
|
checkSlugAvailability,
|
||||||
setActiveOrganization
|
setActiveOrganization,
|
||||||
|
listMembers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { createAuthClient } from 'better-auth/vue'
|
|||||||
import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client'
|
import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client'
|
||||||
import { organizationClient, jwtClient } from 'better-auth/client/plugins'
|
import { organizationClient, jwtClient } from 'better-auth/client/plugins'
|
||||||
import type { RouteLocationRaw } from 'vue-router'
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
import type { RouteLocationNormalizedLoaded } from '#vue-router'
|
|
||||||
import {
|
import {
|
||||||
accessControl,
|
accessControl,
|
||||||
employerRole,
|
employerRole,
|
||||||
@@ -29,6 +28,10 @@ export type User = Session['user']
|
|||||||
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
|
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
|
||||||
export type Organization = Client['$Infer']['Organization']
|
export type Organization = Client['$Infer']['Organization']
|
||||||
export type Invitation = Client['$Infer']['Invitation']
|
export type Invitation = Client['$Infer']['Invitation']
|
||||||
|
export type Member = Client['$Infer']['Member']
|
||||||
|
export type ListMembersOptions = Parameters<Client['organization']['listMembers']>[0]
|
||||||
|
export type ListMembersResponse = Awaited<ReturnType<Client['organization']['listMembers']>>
|
||||||
|
export type ListMembersQuery = NonNullable<ListMembersOptions>['query']
|
||||||
|
|
||||||
// Extended invitation type with additional organization and inviter details
|
// Extended invitation type with additional organization and inviter details
|
||||||
export type CustomInvitation =
|
export type CustomInvitation =
|
||||||
@@ -45,14 +48,21 @@ const user = ref<InferUserFromClient<ClientOptions> | null>(null)
|
|||||||
const sessionFetching = import.meta.server ? ref(false) : ref(false)
|
const sessionFetching = import.meta.server ? ref(false) : ref(false)
|
||||||
const jwt = ref<string | null>(null)
|
const jwt = ref<string | null>(null)
|
||||||
const organizations = ref<
|
const organizations = ref<
|
||||||
{ id: string; name: string; createdAt: Date; slug: string; metadata?: any; logo?: string | null }[]
|
{
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
createdAt: Date
|
||||||
|
slug: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
logo?: string | null
|
||||||
|
}[]
|
||||||
>([])
|
>([])
|
||||||
const selectedOrganization = ref<{
|
const selectedOrganization = ref<{
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
slug: string
|
slug: string
|
||||||
metadata?: any
|
metadata?: Record<string, unknown>
|
||||||
logo?: string | null
|
logo?: string | null
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const activeMember = ref<{ role: string } | null>(null)
|
const activeMember = ref<{ role: string } | null>(null)
|
||||||
|
|||||||
@@ -56,9 +56,9 @@
|
|||||||
<!-- Members -->
|
<!-- Members -->
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="font-medium mb-2">Members</p>
|
<p class="font-medium mb-2">Members</p>
|
||||||
<div v-if="activeOrganization?.members" class="space-y-2">
|
<div v-if="activeOrganizationMembers" class="space-y-2">
|
||||||
<div
|
<div
|
||||||
v-for="member in activeOrganization.members"
|
v-for="member in activeOrganizationMembers"
|
||||||
:key="member.id"
|
:key="member.id"
|
||||||
class="flex justify-between items-center"
|
class="flex justify-between items-center"
|
||||||
>
|
>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user && canRemove({ role: 'owner' }, member)">
|
<div v-if="user && canRemove({ role: 'owner' }, member)">
|
||||||
<UButton size="xs" color="error" @click="organization.removeMember({ memberIdOrEmail: member.id })">
|
<UButton size="xs" color="error" @click="organizationStore.removeMember(member.id)">
|
||||||
{{ member.user.id === user.id ? 'Leave' : 'Remove' }}
|
{{ member.user.id === user.id ? 'Leave' : 'Remove' }}
|
||||||
</UButton>
|
</UButton>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,15 +140,14 @@ import { useClipboard } from '@vueuse/core'
|
|||||||
import type { Invitation } from 'better-auth/plugins'
|
import type { Invitation } from 'better-auth/plugins'
|
||||||
|
|
||||||
const { copy, copied } = useClipboard()
|
const { copy, copied } = useClipboard()
|
||||||
const { organization, user } = useAuth()
|
const { user } = useAuth()
|
||||||
const organizationStore = useOrganizationStore()
|
const organizationStore = useOrganizationStore()
|
||||||
const { deleteOrganization: betterAuthDeleteOrganization, loadOrganizations } = organizationStore
|
const { activeOrganization, activeOrganizationMembers, organizations, invitations } = storeToRefs(organizationStore)
|
||||||
const { activeOrganization, organizations, invitations } = storeToRefs(organizationStore)
|
|
||||||
|
|
||||||
const isRevoking = ref<string[]>([])
|
const isRevoking = ref<string[]>([])
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadOrganizations()
|
await organizationStore.loadOrganizations()
|
||||||
})
|
})
|
||||||
|
|
||||||
const labeledOrganizations = computed(() => organizations.value.map((org) => ({ label: org.name, value: org.id })))
|
const labeledOrganizations = computed(() => organizations.value.map((org) => ({ label: org.name, value: org.id })))
|
||||||
@@ -187,6 +186,6 @@ async function deleteOrganization() {
|
|||||||
|
|
||||||
if (!confirmed) return
|
if (!confirmed) return
|
||||||
|
|
||||||
await betterAuthDeleteOrganization()
|
await organizationStore.deleteOrganization()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import type { ActiveOrganization, Organization, Invitation } from '~/composables/useAuth'
|
import type {
|
||||||
|
ActiveOrganization,
|
||||||
|
Organization,
|
||||||
|
Invitation,
|
||||||
|
ListMembersOptions,
|
||||||
|
ListMembersResponse,
|
||||||
|
ListMembersQuery
|
||||||
|
} from '~/composables/useAuth'
|
||||||
import { useOrganizationApi } from '~/composables/organization/useOrganizationApi'
|
import { useOrganizationApi } from '~/composables/organization/useOrganizationApi'
|
||||||
import type { LegalRole } from '~/server/utils/permissions'
|
import type { LegalRole } from '~/server/utils/permissions'
|
||||||
|
|
||||||
@@ -6,6 +13,7 @@ export const useOrganizationStore = defineStore('Organization', () => {
|
|||||||
const activeOrganization = ref<ActiveOrganization | null>(null)
|
const activeOrganization = ref<ActiveOrganization | null>(null)
|
||||||
const organizations = ref<Organization[]>([])
|
const organizations = ref<Organization[]>([])
|
||||||
const invitations = ref<Invitation[]>([])
|
const invitations = ref<Invitation[]>([])
|
||||||
|
const activeOrganizationMembers = ref<ListMembersResponse>([])
|
||||||
|
|
||||||
const organizationApi = useOrganizationApi()
|
const organizationApi = useOrganizationApi()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -87,6 +95,19 @@ export const useOrganizationStore = defineStore('Organization', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeMember(memberId: string) {
|
||||||
|
try {
|
||||||
|
await organizationApi.removeMember(memberId)
|
||||||
|
activeOrganizationMembers.value = activeOrganizationMembers.value.filter(
|
||||||
|
(member: Member) => member.id !== memberId
|
||||||
|
)
|
||||||
|
toast.add({ title: 'Member removed successfully', color: 'success' })
|
||||||
|
} catch (e) {
|
||||||
|
toast.add({ title: 'Error removing member', color: 'error' })
|
||||||
|
console.error('Error removing member:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function acceptInvitation(invitationId: string) {
|
async function acceptInvitation(invitationId: string) {
|
||||||
try {
|
try {
|
||||||
await organizationApi.acceptInvitation(invitationId)
|
await organizationApi.acceptInvitation(invitationId)
|
||||||
@@ -157,14 +178,36 @@ export const useOrganizationStore = defineStore('Organization', () => {
|
|||||||
|
|
||||||
const { data: invitationsToSet } = await organizationApi.listInvitations(activeOrganizationToSet?.id)
|
const { data: invitationsToSet } = await organizationApi.listInvitations(activeOrganizationToSet?.id)
|
||||||
invitations.value = invitationsToSet ?? []
|
invitations.value = invitationsToSet ?? []
|
||||||
|
|
||||||
|
await loadMembers()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.add({ title: 'Error setting active organizations', color: 'error' })
|
toast.add({ title: 'Error setting active organizations', color: 'error' })
|
||||||
console.error('Error setting active organizations:', e)
|
console.error('Error setting active organizations:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadMembers(options?: Omit<NonNullable<ListMembersQuery>, 'organizationId'>) {
|
||||||
|
try {
|
||||||
|
if (!activeOrganization.value?.id) return null
|
||||||
|
|
||||||
|
const memberOptions: ListMembersOptions = {
|
||||||
|
query: {
|
||||||
|
organizationId: activeOrganization.value.id,
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: response } = await organizationApi.listMembers(memberOptions)
|
||||||
|
activeOrganizationMembers.value = response?.members ?? []
|
||||||
|
} catch (e) {
|
||||||
|
toast.add({ title: 'Error getting members', color: 'error' })
|
||||||
|
console.error('Error getting members:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeOrganization,
|
activeOrganization,
|
||||||
|
activeOrganizationMembers,
|
||||||
organizations,
|
organizations,
|
||||||
createOrganization,
|
createOrganization,
|
||||||
deleteOrganization,
|
deleteOrganization,
|
||||||
@@ -172,6 +215,7 @@ export const useOrganizationStore = defineStore('Organization', () => {
|
|||||||
getInvitation,
|
getInvitation,
|
||||||
loadInvitations,
|
loadInvitations,
|
||||||
inviteMember,
|
inviteMember,
|
||||||
|
removeMember,
|
||||||
acceptInvitation,
|
acceptInvitation,
|
||||||
rejectInvitation,
|
rejectInvitation,
|
||||||
cancelSentInvitation,
|
cancelSentInvitation,
|
||||||
|
|||||||
Reference in New Issue
Block a user