feat(frontend): Refactor organization, fixed several organization bugs
This commit is contained in:
@@ -69,9 +69,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CustomInvitation } from '~/composables/useAuth'
|
||||
|
||||
const invitationId = useRoute().params.id as string
|
||||
|
||||
const { acceptInvitation, rejectInvitation, getInvitation } = useBetterAuth()
|
||||
const { acceptInvitation, rejectInvitation, getInvitation } = useOrganizationStore()
|
||||
const invitation = ref<CustomInvitation>(null)
|
||||
const invitationStatus = ref<'pending' | 'accepted' | 'rejected'>('pending')
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user