fix(frontend): Loading and removal of members of organization

This commit is contained in:
2025-09-28 09:27:23 +02:00
parent 19056d5e75
commit ec12bacca5
4 changed files with 77 additions and 13 deletions

View File

@@ -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
} }
} }

View File

@@ -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)

View File

@@ -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>

View File

@@ -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,