feat(frontend): Refactor organization, fixed several organization bugs

This commit is contained in:
2025-08-17 09:28:26 +02:00
parent b7b6d02cf2
commit 6090d543c1
14 changed files with 279 additions and 221 deletions

View File

@@ -23,13 +23,13 @@
>
<div class="flex justify-between items-center">
<USelect
v-model="selectedOrgId"
:items="availableOrganizations"
v-model="selectedOrganizationId"
:items="labeledOrganizations"
value-key="value"
placeholder="Select organization"
class="w-64"
/>
<CreateOrganizationModal @organization-created="setOrganization" />
<CreateOrganizationModal />
</div>
</UPageCard>
@@ -89,11 +89,9 @@
<div class="flex-1">
<p class="font-medium mb-2">Invites</p>
<div class="space-y-2">
<template v-if="activeOrganization?.invitations">
<template v-if="invitations.length > 0">
<div
v-for="invitation in activeOrganization?.invitations.filter(
(i: Invitation) => i.status === 'pending'
)"
v-for="invitation in invitations.filter((i: Invitation) => i.status === 'pending')"
:key="invitation.id"
class="flex justify-between items-center"
>
@@ -106,7 +104,7 @@
size="xs"
color="error"
:loading="isRevoking.includes(invitation.id)"
@click="() => handleRevokeInvitation(invitation.id)"
@click="() => handleInvitationCancellation(invitation.id)"
>
Revoke
</UButton>
@@ -116,9 +114,7 @@
</div>
</div>
</template>
<p v-if="!activeOrganization?.invitations?.length" class="text-sm text-gray-500">
No active invitations
</p>
<p v-else class="text-sm text-gray-500">No active invitations</p>
<p v-if="!activeOrganization?.id" class="text-xs text-gray-500">
You can't invite members to your personal workspace.
</p>
@@ -144,41 +140,26 @@ import { useClipboard } from '@vueuse/core'
import type { Invitation } from 'better-auth/plugins'
const { copy, copied } = useClipboard()
const toast = useToast()
const { organization } = useAuth()
const { deleteOrganization: betterAuthDeleteOrganization, activeOrganization, selectedOrgId } = useBetterAuth()
const organizations = ref<
Array<{ id: string; name: string; slug: string; createdAt: Date; logo?: string | null; metadata?: unknown }>
>([])
const availableOrganizations = computed(() => organizations.value.map((org) => ({ label: org.name, value: org.id })))
const { user } = await useAuth()
const { organization, user } = useAuth()
const organizationStore = useOrganizationStore()
const { deleteOrganization: betterAuthDeleteOrganization, loadOrganizations } = organizationStore
const { activeOrganization, organizations, invitations } = storeToRefs(organizationStore)
const isRevoking = ref<string[]>([])
watch(selectedOrgId, async (newId) => {
const { data } = await organization.setActive({ organizationId: newId || null })
activeOrganization.value = data
onMounted(async () => {
await loadOrganizations()
})
await loadOrganizations()
async function loadOrganizations() {
try {
const response = await organization.list()
organizations.value = response.data || []
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to load organizations'
toast.add({ title: 'Error', description: errorMessage, color: 'error' })
const labeledOrganizations = computed(() => organizations.value.map((org) => ({ label: org.name, value: org.id })))
const selectedOrganizationId = computed({
get() {
return activeOrganization.value?.id
},
set(id: string) {
organizationStore.setActiveOrganization(id)
}
}
async function setOrganization(id: string | undefined) {
await loadOrganizations()
selectedOrgId.value = id
}
})
function isAdminOrOwner(member: { role: string }) {
return member.role === 'owner' || member.role === 'admin'
@@ -188,26 +169,9 @@ function canRemove(current: { role: string }, target: { role: string }) {
return target.role !== 'owner' && isAdminOrOwner(current)
}
async function handleRevokeInvitation(invitationId: string) {
async function handleInvitationCancellation(invitationId: string) {
isRevoking.value.push(invitationId)
await organization.cancelInvitation(
{ invitationId },
{
onSuccess: () => {
toast.add({ title: 'Success', description: 'Invitation revoked', color: 'success' })
isRevoking.value = isRevoking.value.filter((id) => id !== invitationId)
if (activeOrganization.value) {
activeOrganization.value.invitations = activeOrganization.value.invitations.filter(
(invitation: Invitation) => invitation.id !== invitationId
)
}
},
onError: (ctx) => {
toast.add({ title: 'Error', description: ctx.error.message, color: 'error' })
isRevoking.value = isRevoking.value.filter((id) => id !== invitationId)
}
}
)
await organizationStore.cancelSentInvitation(invitationId)
}
function getInviteLink(inviteId: string): string {